Excel VBA: how to get a link to a shape from ChartObject

I am trying to get a link to a Shape in Worksheet matching a ChartObject . I have not found a specific way to do this. The only approximation by trial and error and just verified in several cases suggests that ZOrder a ChartObject matches 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 small excess of certain variables is used for debugging purposes).

Is there any other specific way to do this?

Any identifier used to select the correct Shape must 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 ) not guaranteed to work. Index / ZOrderPosition is just an assumption, at least satisfying the requirement of uniqueness.

Edit : see @Andres answer in Excel VBA: Index = ZOrderPosition in the Shapes collection? . It is clear that ZOrder a ChartObject not equal to Index or ChartObject , or to the corresponding Shape (and I checked it). But it turns out that ZOrder is equal to ZOrderPosition corresponding Shape . This has been confirmed 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 
+6
source share
2 answers

Having lost hours in a similar problem, I found a couple of concepts related to linking shapes in excel, but none of them satisfy me 100%. To access the form you have 4 clean methods:

  • Shape.Name : FAST, but NOT RELIABLE. The form name can be used to get a link to the shape, but provided that you do not have duplicate names. Code: ActiveSheet.Shapes("Shape1")

  • Shape.ZOrderPosition : very FAST, but NOT RELIABLE. You can use the ZOrder of a form to get a link to a shape, because it is the same as the shape index in the shape collection. But if you do not have a group of figures that violates the previous rule (see fooobar.com/questions/955144 / ... ). Code: ActiveSheet.Shapes(ZOrderFromOneShape)

  • Set shpRef = Shape : FAST, RELIABLE, but NOT CONSTANT. I try to use it always, I can, especially when I create a new form. Moreover, if I have to repeat new forms later, I try to keep a reference to an object inside the collection. However, not Persistent, this means that if you run VBA code again and again to lose all links and collections. Code: Set shp = NewShape , or you can add it to the collection: coll.add NewShape for the loop later.

  • Shape.ID : RELIABLE, SIDE, but not directly supported! The form identifier is very reliable (do not change and cannot duplicate identifiers in the sheet). However, there is no direct VBA function to return a form knowing its identifier. The only way is to completely process all the shapes until the identifier matches the ID you were looking for, but it can be very SLOW! .

code:

 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 

Note 1: If you want to access this function several times, you can improve it using the cache of form identifiers. This way you will loop only once.
Note 2: If you move a shape from one sheet to another, the shape identifier will change!


Mixing and using the above knowledge, I concluded two main approaches:

FIRST APPROACH

  • FAST, BUT VOLATILE: (same as item number 3). Try to keep the link in the object as long as possible. When I have to repeat a bunch of shapes later, I keep the links inside the collection, and I avoid using other secondary links, such as name, ZOrder or ID.

For instance:

 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 

Of course, the problem is that collection and reference are not permanent. You will lose them when you stop and restart the vba code!

SECOND APPROACH

  • PERSISTENT: My solution is to keep the name and ID of the form for later reference. What for? Having a name, I can access the form very quickly most of the time. Just in case, when I found a duplicate name, I do a slow ID search loop. How to find out if there is a duplicate name? Very simple, just check the identifier of the first name search, and if they do not match, you should assume that this is duplicated.

Here is the code:

 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 

Hope this helps you!


Note 1: if you want to search for shapes that can be inside groups, then the function is more complicated.

Note 2: ZOrder looks good, but cannot find it useful. When I tried to take advantage of this, there was always a missing part ...

+9
source

@TimWilliams is almost right (in his comment). However, there is a situation where Timโ€™s idea can lead to confusing results.

I think the following code will be more appropriate and correct.

 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 
-1
source

Source: https://habr.com/ru/post/955141/


All Articles