Excel VBA:如何从 ChartObject 获取对形状的引用 [英] Excel VBA: How to obtain a reference to a Shape from the ChartObject
问题描述
我正在尝试获取对 Worksheet
中的 Shape
的引用,对应于 ChartObject
.我没有找到这样做的特定方法.通过反复试验并在少数情况下进行简单测试,唯一的近似值是假设 ChartObject
的 ZOrder
与相应 ChartObject
的索引相同代码>形状代码>:
I am trying to obtain a reference to a Shape
in a Worksheet
, corresponding to a ChartObject
. I found no certain way of doing this. The only approximation, by trial-and-error and simply tested in a few cases, is assuming that the ZOrder
of a ChartObject
is the same as the Index of the corresponding Shape
:
Function chobj2shape(ByRef cho As ChartObject) As Shape
' It appears that the ZOrder of a ChartObject is the same as the Index of
' the corresponding Shape, which in turn appears to be the same as its ZOrderPosition
Dim zo As Long
Dim ws As Worksheet
Dim shc As Shapes
Dim sh As Shape
zo = cho.ZOrder
Set ws = cho.Parent
Set shc = ws.Shapes
Set sh = shc.Item(zo)
Set chobj2shape = sh
'Set sh = Nothing
End Function
(稍微过量的定义变量用于调试目的).
(a slight excess of defined variables is used for debugging purposes).
还有更确定的方法吗?
用于选择正确Shape
的任何标识符都应该是唯一的.该名称不一定是唯一的(参见 https://stackoverflow.com/questions/19153331/duplicated-excel-chart-has-the-same-name-name-as-the-original-instead-of-increm),所以它不能保证工作.Index
/ZOrderPosition
只是一个猜测,至少满足唯一性的要求.
Any identifier used for picking the correct Shape
should be unique. The name is not necessarily unique (see https://stackoverflow.com/questions/19153331/duplicated-excel-chart-has-the-same-name-name-as-the-original-instead-of-increm), so it is not guaranteed to work. The Index
/ZOrderPosition
is just a guess, at least satisfying the requirement of uniqueness.
编辑:在 Excel VBA:形状集合中的索引 = ZOrderPosition?.很明显,ChartObject
的 ZOrder
不等于 ChartObject
或相应的 Index
Shape
(我已经验证过了).但看起来ZOrder
等于对应Shape
的ZOrderPosition
.这已通过 dump_chartobjects
验证:
Edit: see answer by @Andres in Excel VBA: Index = ZOrderPosition in a Shapes collection?. It is clear that the ZOrder
of a ChartObject
is not equal to the Index
of either the ChartObject
or the corresponding Shape
(and I have verified this).
But it appears that ZOrder
is equal to ZOrderPosition
of the corresponding Shape
. This was verified with dump_chartobjects
:
Sub dump_chartobjects()
' Dump information on all ChartObjects in a Worksheet.
Dim coc As ChartObjects
Set coc = ActiveSheet.ChartObjects
Dim cho As ChartObject
Dim ich As Long
For ich = 1 To coc.Count
Dim msg As String
Set cho = coc(ich)
With cho
msg = "ChartObject '" & .name & "'" _
& ", type name: " & TypeName(cho) & ", at: " & .TopLeftCell.Address _
& ", index: " & ich & ", .Index: " & .Index _
& ", ZOrder: " & .ZOrder
'& ", hyperlink: " & .Hyperlink
End With
Debug.Print msg
Dim ish As Long
ish = choidx2shpidx(ich, coc.Parent)
Next ich
End Sub
Function choidx2shpidx(coidx As Long, ws As Worksheet) As Long
Dim cozo As Long
Dim coc As ChartObjects
Dim co As ChartObject
Set coc = ws.ChartObjects
Set co = coc(coidx)
cozo = co.ZOrder
choidx2shpidx = zo2idx_shp(cozo, ws)
Dim con As String, shn As String
Dim sh As Shape
Set sh = ws.Shapes(choidx2shpidx)
con = co.name
shn = sh.name
Dim cox As Double, coy As Double
Dim cow As Double, coh As Double
Dim shx As Double, shy As Double
Dim shw As Double, shh As Double
cox = co.Left
coy = co.top
cow = co.Width
coh = co.Height
shx = sh.Left
shy = sh.top
shw = sh.Width
shh = sh.Height
If ((con <> shn) Or (cox <> shx) Or (coy <> shy) Or (cow <> shw) Or (coh <> shh)) Then
Dim msg As String
msg = "ChartObject: '" & con & "', Shape: '" & shn & "'"
'Debug.Print msg
MsgBox msg
choidx2shpidx = -1
End If
End Function
Function zo2idx_shp(zo As Long, ws As Worksheet) As Long
Dim ish As Long
Dim shc As Shapes
Dim sh As Shape
Set shc = ws.Shapes
For ish = 1 To shc.Count
Set sh = shc(ish)
If (sh.ZOrderPosition = zo) Then
zo2idx_shp = ish
Exit Function
End If
Next ish
zo2idx_shp = -1
End Function
推荐答案
在一个类似的问题上浪费了几个小时后,我发现了几个与在 excel 中引用形状相关的概念,但没有一个让我 100% 满意.要访问形状,您有 4 种纯方法:
After losing hours in a similar issue, I found a couple of concepts related to referencing shapes in excel, but none satisfies me 100%. For accessing a shape you have 4 pure methods:
Shape.Name:速度快,但不可靠.形状的名称可用于获取形状的引用,但前提是您没有重复的名称.代码:
ActiveSheet.Shapes("Shape1")
Shape.Name : Is FAST, but NOT RELIABLE. The name of the shape could be used to get a reference of a shape but provided you don't have duplicated names. Code:
ActiveSheet.Shapes("Shape1")
Shape.ZOrderPosition:非常快,但不可靠.形状的 ZOrder 可用于获取形状的引用,因为它与形状集合中形状的索引相同.但前提是您没有破坏先前规则的形状组(请参阅:https://stackoverflow.com/a/19163848/2843348).代码:ActiveSheet.Shapes(ZOrderFromOneShape)
Shape.ZOrderPosition : Very FAST, but NOT RELIABLE. The ZOrder of the shape could be used to get a reference of a shape, because is the same as the index of the shape in the shapes collection. But provided you don't have group of shapes that breaks previous rule (See: https://stackoverflow.com/a/19163848/2843348). Code: ActiveSheet.Shapes(ZOrderFromOneShape)
设置 shpRef=Shape:快速、可靠,但不持久.我总是尽可能地使用它,特别是当我创建一个新形状时.此外,如果我稍后必须迭代新形状,我会尝试将对象引用保留在集合中.但是不是 Persistent,这意味着如果您停止并再次运行 VBA 代码,将丢失所有引用和集合.代码:设置 shp = NewShape
,或者您可以将其添加到集合中:coll.add NewShape
for 稍后循环.
Set shpRef=Shape: FAST, RELIABLE, but NOT PERSISTENT. I try to use this always I can, specially when I create a new shape. Moreover, if I have to iterate on the new shapes later one I try to keep the object reference inside a collection. However not Persistent, that means if you stop and run you VBA code again to will loose all the references and collection. Code: Set shp = NewShape
, or you can add it to a collection: coll.add NewShape
for loop it later on.
Shape.ID:可靠、持久,但不直接支持!形状的 ID 非常可靠(不要更改,也不能是 Sheet 中的重复 ID).但是,没有直接的 VBA 函数来获取知道其 ID 的形状.唯一的方法是遍历所有形状,直到 ID 与您要查找的 ID 匹配,但这可能非常慢!.
Shape.ID : RELIABLE, PERSISTENT, but not directly supported! The ID of the shape is very reliable (don't change and cannot be duplicates IDs in a Sheet). However, there is no direct VBA function to get a shape back knowing its ID. The only way is to loop thorough all shapes until the ID match the ID you was looking for, but this can be very SLOW!.
代码:
Function FindShapeByID(ws as excel.worksheet, ID as long) as Excel.Shape
dim i as long
set FindShapeByID = nothing 'Not found...
for i = 1 to ws.shapes.count
if ws.shapes(i).ID = ID then
set FindShapeByID = ws.shapes(i) 'Return the shape object
exit function
end if
next i
End Function
注意 1:如果您想多次访问此功能,可以通过使用形状 ID 缓存来改进它.这样你只会循环一次.
注意 2:如果将形状从一张纸移动到另一张纸,形状的 ID 会改变!
Note 1: If you want to access this function several times, you can improve it by using a cache of Shape IDs. That way you will make the loop only one time.
Note 2: If you move a shape from one sheet to other, the ID of the shape will change!
通过混合和使用上述知识,我得出了两种主要方法:
By mixing and using above knowledge, I have concluded in two main approaches:
- 最快但易变:(与第 3 点相同)尽量将引用保留在对象中.当我稍后必须遍历一堆形状时,我将引用保存在一个集合中,并避免使用其他辅助引用,如名称、ZOrder 或 ID.
例如:
dim col as new Collection
dim shp as Excel.Shape
'' <- Insert the code here, where you create your shape or chart
col.add shp1
'' <- Make other stuffs
for each shp in col
'' <- make something with the shape in this loop!
next shp
问题当然是收集和引用不是永久的.当您停止并重新启动 vba 代码时,您将丢失它们!
The problem of course is that the collection and reference are not permanent. You will loose them when you stop and restart the vba code!
- PERSISTENT:我的解决方案是保存形状的名称和ID以供以后参考.为什么?有了名字,我可以在大多数情况下非常快速地访问形状.以防万一我发现了重复的名称,我会慢速循环搜索 ID.我怎么知道是否有重复的名称?很简单,只需检查名字搜索的ID,如果它们不匹配,您必须假设是重复的.
- PERSISTENT: My solution is to save the name and the ID of the shape for later reference. Why? Having the name I can access the shape very fast most of the time. Just in case I found a duplicated name I make the slow loop searching the ID. How can I know if there is a name duplicated? Very simple, just check the ID of the first name search, and if they don't match you have to suppose is duplicated.
代码如下:
Function findShapeByNameAndID(ws As Excel.Worksheet, name As String, ID As Long) As Shape
Dim sh As Excel.Shape
Set findShapeByNameAndID = Nothing 'Means not found
On Error GoTo fastexit
Set sh = ws.Shapes(name)
'Now check if the ID matches
If sh.ID = ID Then
'Found! This should be the usual case!
Set findShapeByNameAndID = sh
Else
'Ups, not the right shape. We ha to make a loop!
Dim i As Long
For i = 1 To ws.Shapes.Count
If ws.Shapes(i).ID = ID Then
'Found! This should be the usual case!
Set findShapeByNameAndID = ws.Shapes(i)
End If
Next i
End If
fastexit:
Set sh = Nothing
End Function
希望对你有帮助!
注1:如果你想搜索可能在组内的形状,那么功能更复杂.
Note 1: Is you want to search shapes that maybe inside groups, then the function is more complicated.
注意 2:ZOrder 看起来不错,但发现它没有用.当我试图利用它时,总是有一个缺失的部分......
Note 2: The ZOrder looks nice, but cannot find it useful. When I tried to take advantage of it, there was always a missing part...
这篇关于Excel VBA:如何从 ChartObject 获取对形状的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!