Excel VBA:如何从ChartObject获取对Shape的引用

时间:2013-10-02 20:22:56

标签: excel vba charts

我正在尝试获取Shape中与Worksheet相对应的ChartObject的引用。我发现没有办法做到这一点。通过反复试验并在少数情况下进行简单测试的唯一近似假设ZOrder的{​​{1}}与相应ChartObject的索引相同:< / p>

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 的任何标识符都应该是唯一的。该名称不一定是唯一的(请参阅https://stackoverflow.com/questions/19153331/duplicated-excel-chart-has-the-same-name-name-as-the-original-instead-of-increm),因此无法保证其有效。 Shape / Index只是猜测,至少可以满足唯一性的要求。

修改:在Excel VBA: Index = ZOrderPosition in a Shapes collection?中查看@Andres的回答。很明显,ZOrderPosition的{​​{1}}不等于ZOrder或相应ChartObject的{​​{1}}(我已经验证了这一点) 。 但似乎Index等于相应ChartObject的{​​{1}}。这已通过Shape验证:

ZOrder

2 个答案:

答案 0 :(得分:9)

在类似问题中丢失了几个小时后,我发现了几个与excel中引用形状相关的概念,但没有一个能让我满意100%。要访问形状,您有4种纯方法:

  1. Shape.Name :快速,但不可靠。形状的名称可用于获取形状的引用,但前提是您没有重复的名称。代码:ActiveSheet.Shapes("Shape1")

  2. Shape.ZOrderPosition :非常快,但不可靠。形状的ZOrder可用于获取形状的引用,因为它与形状集合中的形状的索引相同。但是,如果您没有违反先前规则的形状组(请参阅:https://stackoverflow.com/a/19163848/2843348)。代码:ActiveSheet.Shapes(ZOrderFromOneShape)

  3. 设置shpRef = Shape :FAST,RELIABLE,但不是PERSISTENT。我总是尽力使用它,特别是当我创建一个新形状时。此外,如果我必须稍后迭代新形状,我会尝试将对象引用保留在集合中。但是不是Persistent,这意味着如果你再次停止并运行VBA代码将会丢失所有引用和集合。代码:Set shp = NewShape,或者您可以将其添加到集合中:coll.add NewShape以便稍后循环播放。

  4. Shape.ID :RELIABLE,PERSISTENT,但不直接支持!形状的ID非常可靠(不要更改,也不能重复工作表中的ID)。但是,没有直接的VBA功能可以知道其ID的形状。唯一的方法是遍历所有形状,直到ID与您要查找的ID匹配,但这可能非常慢!

  5. 代码:

    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 :如果要多次访问此功能,可以使用Shape ID缓存来改进它。这样你只会循环一次。
    注意2 :如果您将形状从一个工作表移动到另一个工作表,形状的ID将会改变!


    通过混合和使用上述知识,我得出两个主要方法:

    第一种方法

    • 最快但易挥发:(与第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代码时,你将松开它们!

    第二种方法

    • PERSISTENT:我的解决方案是保存形状的名称 ID 以供日后参考。为什么?有了这个名字,我可以在大多数时间内快速访问形状。为了防止我找到一个重复的名称,我会慢速搜索ID。我怎么知道是否有重复的名称?很简单,只需检查名字搜索的ID,如果它们不匹配,则必须假设是重复的。

    这里是代码:

    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:您是否要搜索可能在组内的形状,然后该功能更复杂。

    注2:ZOrder看起来不错,但找不到它有用。当我试图利用它时,总会有一个缺失的部分......

答案 1 :(得分:-1)

@TimWilliams几乎是正确的(在他的评论中)。然而,在某些情况下,蒂姆的想法可能会让人感到困惑。

我认为以下代码更合适也更正确。

Sub qTest()

    Dim cho As ChartObject
    Set cho = ActiveSheet.ChartObjects(1)

    Dim SH As Shape
    Set SH = cho.ShapeRange.Item(1)

    SH.Select 'here Shape will be selected..
    Debug.Print TypeName(SH) '...which we can check here
End Sub