Weird behavior attempting to create visio selection if shape is part of a group

Started by JM, April 10, 2018, 06:46:15 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

JM

So I have been doing quite a bit of debugging/troubleshooting of this and I honestly can't explain why I am seeing the behavior I am seeing.  Perhaps so of the guru's here can assist.

What I am trying to do (50k view):
I have a macro to take a shape (either from a right-click action menu, or from a double-click event) and create a selection (visio.selection) of the shape and all shapes it is connected to.

What I am experiencing:
Normally, the code I am using works fine.  It adds the shapes to a visio.selection object and then sets the activepage.selection to that selection object.  No problem.
HOWEVER, if I have one of the connected shapes as part of a group, then the logic appears to completely walk all over itself in adding shapes to the visio.selection object.   I have sat watching the selection object in the debugger and see the selected objects change and then disappear (!!??) from the object list when doing:

Call vsoSelection.Select(connectedShape, Visio.VisSelectArgs.visSelect)

I have tried changing the selection object's IterationMode behavior (e.g. visSelModeSkipSuper), but that doesn't seem to make a difference.
At this point, I am wondering whether there is some deeper layer of understanding that I am missing, or whether this might be an actual bug..

Here is a pared down code snippet for the routine:
Public Sub SelectAllConnections(pCallingShape As Visio.Shape, Optional ByVal pIncludeLinks As Boolean = False)
Dim vsoSelection As Visio.Selection
Dim lngShapeIDs() As Long  'This is the list of shape IDs to select
Dim lngShapeIDs2() As Long  'If the calling shape is a 2-D shape, then this will hold the 1-D shapes (links) connected to the shape
Dim connectedShape As Visio.Shape
Dim pShapeID as Variant
Dim i As Integer
Dim currentPage As Visio.Page
Dim currentApplication As Visio.Application

        Set currentApplication = pCallingShape.Application
        Set currentPage = currentApplication.ActiveWindow.Page
               
        'Pick up the objects that are already selected
        Set vsoSelection = ActiveWindow.Selection
       
        'I tried using this below to create the selection object.. no difference.
        'Set vsoSelection = ActivePage.CreateSelection(visSelTypeEmpty, visSelModeSkipSuper, pCallingShape)

        'Change the iteration mode to prevent problems with selecting children of a group
        'vsoSelection.IterationMode = Visio.VisSelectMode.visSelModeSkipSuper
   
        'Make sure the passed shape is selected. (If somehow this procedure is called directly)
        Call vsoSelection.Select(pCallingShape, Visio.VisSelectArgs.visSelect)
   
        'Get the connected shapes for the shape which was passed in
        If (pCallingShape.OneD) Then
            ' The calling shape is a line (connector), so get the shapes to add through a different method
            ' This currently isnt used
            lngShapeIDs = pCallingShape.GluedShapes(visGluedShapesAll2D, "")
        Else
            lngShapeIDs = pCallingShape.ConnectedShapes(visConnectedShapesAllNodes, "")
        End If
   
        'Adding in the 1D shapes also, in case a line is connected to a line.. Would have to add code to pick up
        'any 2D shape at the end of the line-line connection
        If (pIncludeLinks) Then
            lngShapeIDs2 = pCallingShape.GluedShapes(visGluedShapesAll1D, "")
        Else
            'skip adding the links
        End If

        'I didn't include this code, but  it never changes the lngShapeIDs array in my test cases.
        Call ConcatenateArrays(lngShapeIDs, lngShapeIDs2)
       
        For Each pShapeID In lngShapeIDs

            Set connectedShape = currentPage.Shapes.ItemFromID(pShapeID)
           
              'Add to the selection object
                 Call vsoSelection.Select(connectedShape, Visio.VisSelectArgs.visSelect)
           
        Next pShapeID
       
        'Now set the selection in the window to the selection object
         Application.ActiveWindow.Selection = vsoSelection
   
End Sub


In my debugging, I have seen the following:

lngShapeIDs:  18, 266, 452, 514
Calling shapeID is: 80

At the start, the Selection object has (because I added it at the start):
Item1: 80

After first pass in the loop the Selection Object has:
Item1: 18    (!??!?)

After second pass in the loop, the selection Object has:
Item1:18
Item2: 266

After the third pass in the loop, the selection Object has:
Item1: 452  (!!??!)

After the fourth pass in the loop, the selection Object has:
Item1: 452
Item2: 514

I believe that 18 and 262 are the ones that belong to a group.

Any thoughts or ideas?


Yacine

This is probably because you can't select shapes belonging to different parents in the UI neither.
The result would be weird anyway.
You may try collecting the shapes in an other data structure (array, collection, ...).
Yacine

wapperdude

Visio 2019 Pro

JM

Quote from: wapperdude on April 11, 2018, 02:18:51 AM
Have you tried the shapes connected method?  https://msdn.microsoft.com/en-us/vba/visio-vba/articles/shape-connectedshapes-method-visio

Wapperdude

Sorry, Wapperdude, I am not understanding where you are going ... yes, I already have that in my code:
lngShapeIDs = pCallingShape.ConnectedShapes(visConnectedShapesAllNodes, "")

But that isn't the problem I am encountering.  I can create the list of shape IDs just fine.  It is creating a selection object with those shapes that is the problem.

JM

Quote from: Yacine on April 10, 2018, 09:33:46 PM
This is probably because you can't select shapes belonging to different parents in the UI neither.
The result would be weird anyway.
You may try collecting the shapes in an other data structure (array, collection, ...).

Hmmm.. so I tested your theory, and (to me, at least) it looks like you _can_ create a selection manually in the UI (see attached pic). (BTW, I am using Microsoft Visio 2016 Professional).  Am I missing something?

I have an array of shapeIDs already, through this routine.. but what I need is to actually make the activewindow.selection contain these shapes.

Any other thoughts/ideas?

wapperdude

Ah.  Missed that.  Sorry.   I can't try your code, as V2007 doesn't have all of the VBA features that you're using...the object model has been updated a couple of times.

Wapperdude
Visio 2019 Pro

wapperdude

I vaguely recall an issue with the "subselect" option...haven't tracked it down yet.  But it occurs to me that you may not need it.  The connects method gives you the shape ID, then, if you make that shape an activev selection, you could add it to your collection array directly without using the subsequent feature.

'Based upon ID:
'    Set vsoShp = ActivePage.Shapes.ItemFromID(1)
'    ActiveWindow.Select vsoShp, visSelect   'This makes the shape ACTIVE.  Now add to your collection array.

Seems like it should work.



Visio 2019 Pro

JM

Ok, I appreciate the effort though.

So I some manual testing and debugging, after considering Yacine's post, and the pic is (I think) a core issue (probably in my understanding).
I created a group of those three shapes, clicked on the center one (720), which selected the group.  I clicked on the shape again, to sub-select that shape (720).
I wrote a simple stub and set up a watch variable on ActiveWindow.Selection, and this is what I see:

The object does not have _ANY_ items in the list.. !? 
So that leads me to believe that somehow the internal 'selection' structures aren't exposed to me fully..  if Visio itself clearly has that object as a selected object.
Is my thinking off base?

Quote from: wapperdude on April 11, 2018, 02:20:06 PM
I vaguely recall an issue with the "subselect" option...haven't tracked it down yet.  But it occurs to me that you may not need it.  The connects method gives you the shape ID, then, if you make that shape an activev selection, you could add it to your collection array directly without using the subsequent feature.

'Based upon ID:
'    Set vsoShp = ActivePage.Shapes.ItemFromID(1)
'    ActiveWindow.Select vsoShp, visSelect   'This makes the shape ACTIVE.  Now add to your collection array.

Seems like it should work.

So, I haven't gone back to this style of adding the shapes (directly selecting them with ActiveWindow.Select).. let me try that really quick in a test.

JM


So, this simple test yields the same error I was encountering..  (item 1 is the shape labelled as 720, which is a member of a group).

There must be something I am missing..

Yacine

Although you were right with different parent selection (my bad), this path wasn't that wrong.

I draw some shapes (ID 1,2,3 - 3 being a group containing ID4).
I couldn't select the shapes 1,2,4 directly by code.

I needed to select #3 first, then #4 by:
activewindow.select  ActiveWindow.Page.Shapes.ItemFromID(4), visSubSelect
Yacine

JM


Yacine, I appreciate your help!  I went back and reviewed the documentation for Selection.Select and now it made a bit more sense.  Once I started thinking about what I was seeing in the debugger, it seemed a bit more rational..  I am not quite confident I fully understand where it is storing the parent selections, but I understand why it is storing them differently.

So while I don't really want the SuperSelections to be included (i.e. the parent groups) in the selection, I guess it will be ok for what I am trying to do at the moment.  So I threw together the following code stubs to enable me to not worry about whether it is in a group:

Public Sub test_recursive()
Dim vsoShape As Visio.Shape
Dim vsoSelection As Visio.Selection
    Set vsoShape = ActivePage.Shapes.ItemFromID(1)
    Set vsoSelection = ActiveWindow.Selection
    Call AddSelectedShape(vsoSelection, vsoShape)
    ActiveWindow.Selection = vsoSelection
End Sub

'-------------------------------------------------------------------------
' AddSelectedShape
' A recursive procedure to add a shape (and its parent if it is a group) to a selection
' If there are multiple groups nested, it will continue to walk up the groups until the top and add all the groups
'-------------------------------------------------------------------------

Public Sub AddSelectedShape1(ByRef pvsoSelection As Visio.Selection, ByRef pShape As Visio.Shape)
    ' If the shape's parent is a group, then we need to add the parent before we subselect the shape itself
    If (pShape.Parent.Type = visTypeGroup) Then
        Call AddSelectedShape(pvsoSelection, pShape.Parent)
        'Now add the shape as a subselect (since we know it is a child)
        pvsoSelection.Select pShape, visSubSelect
    Else
        'It doesn't have a group as a parent, so we can add it as a visSelect
        pvsoSelection.Select pShape, visSelect
    End If
   
End Sub


Ran the test and I saw it went through the logic properly (here are the debug messages I spit out, with two nested groups):

QuoteEntered AddSelectedShape for 1:APP.720
1:APP.720 has a parent who is a group (9:Sheet.9)
Entered AddSelectedShape for 9:Sheet.9
9:Sheet.9 has a parent who is a group (26:Sheet.26)
Entered AddSelectedShape for 26:Sheet.26
Adding 26:Sheet.26 to selection via Select
Exiting AddSelectedShape for 26:Sheet.26
Adding 9:Sheet.9 to selection via subSelect
Exiting AddSelectedShape for 9:Sheet.9
Adding 1:APP.720 to selection via subSelect
Exiting AddSelectedShape for 1:APP.720

However, when I went to the drawing, nothing was selected... Hmm..
On a whim, I swapped the use of a separate selection object with the ActiveWindow.Selection:

Public Sub test_recursive()
Dim vsoShape As Visio.Shape
Dim vsoSelection As Visio.Selection
Set vsoShape = ActivePage.Shapes.ItemFromID(1)

    Set vsoSelection = ActiveWindow.Selection
   
    Call AddSelectedShape(vsoShape)
   
End Sub
'-------------------------------------------------------------------------
' AddSelectedShape
' A recursive procedure to add a shape (and its parent if it is a group) to a selection
' If there are multiple groups nested, it will continue to walk up the groups until the top and add all the groups
'-------------------------------------------------------------------------

Public Sub AddSelectedShape(ByRef pShape As Visio.Shape)

    ' If the shape's parent is a group, then we need to add the parent before we subselect the shape itself
    If (pShape.Parent.Type = visTypeGroup) Then
        Call AddSelectedShape(pShape.Parent)
        'Now add the shape as a subselect (since we know it is a child)
         ActiveWindow.Select pShape, visSubSelect
    Else
        'It doesn't have a group as a parent, so we can add it as a visSelect
        ActiveWindow.Select pShape, visSelect
    End If
   
End Sub


And that worked!  So I find it odd that it would work when using the ActiveWindow.Selection, but not a Visio.Selection.. ? Maybe my use of :
ActiveWindow.Selection = vsoSelection
isn't correct?


wapperdude

QuoteCode: [Select]
ActiveWindow.Selection = vsoSelection
isn't correct?

This is, indeed, incorrect.  Active Window.Selection takes it's value directly from the page's selected objects.  You cannot assign a value to it...at least, not to my knowledge.  However, the opposite is valid:

    Set vsoSelection =  ActiveWindow.Selection



Obviously, I can't see all that you're doing, and I realise you must eliminate shapes that aren't connected to you r chosen shape, but it seems like once you get a valid ID, you can

Set vsoShp = Active Page.Shapes.Itemfromid(shape's I'd)
ActiveWindow.Select gosSip, desElect
Set selEction = Active Window.Selection.


Place this inside your loop.  As the code progresses thru the shapes, the number of selected shapes increases, and so does your vsoSelection.  It shouldn't matter if the identified shape is a subshape in a group or not.  You just need its ID, and that comes from the connects object.

The hard work is filtering out the unwanted shapes, and seems you've handled that.

HTH
Wapperdude
   
Visio 2019 Pro

JM

Ok.. so then what would be the use/purpose/need for the Visio.Selection object ?  If you cannot use it as I did, it seems to me its utility is marginal at best (to just store the activewindow.selection, say, in the process of iterating through the items).

So, I will say, however, that doing that assignment _does_ work (perhaps it wasn't meant to work...).  If there are no groups at all, the code works just fine, and does, in fact, assign the active window selection equal to the contents of my vsoSelection object.

Just using the activewindow.select strategy isn't perfect -- Lets say for example that I start some process to select a bunch of shapes for the user.  I start adding them, adding them, then WOAH.. I encountered something that I didn't want or is meant to stop the process.  If I actively was selecting them using activewindow.select, there would be really no easy way to 'undo' the process to get back to the original state.  (Not sure if that made sense).


wapperdude

Here's a topic re using selection method to get subshapes of a group...see replies by Thomas Winkel.  Going thru that helped me recall issue with selection method...the group must be selected in order to find the subshapes.  There are code examples in the post.

But, since the connections method gives you the needed shape's ID, the selection method is unnecessary.  Hence, the 3 lines of code I showed.  Oops, I see there are auto-correct typos....correct code...


Set vsoShp = Active Page.Shapes.ItemFromID(shape's id)  ' id from connections method
ActiveWindow.Select vsoShp, visSelect                              ' adds shape to selected shapes on the page
Set vsoSelection = Active Window.Selection                       ' updates the collection of selected shapes

Visio 2019 Pro

JM

Thanks for the discussion Wapperdude.

Not to belabor the point, but I think I am not exactly clear in why I was trying to add the shapes to a Selection object and then (all at once) set the active selection to that selection object.

In my 'assembling' of that collection of shapes, i might encounter something (e.g. finding a shape with some flag or something) that might force me to stop what I am doing and error/notify the user.  In that case, the behavior I would have wanted is to _not_ have changed what is actively selected (i.e. I wouldn't want to have added 3 out of 10 of the shapes to the active selection and leave it haphazard).

So, certainly I could just do that logic and store those shapes in just an array of shape IDs (like I am doing).. and then use the ActiveWindow.Select method to do them all at once...    It just puzzles me what the use of the visio.selection object really is, besides someone using it as a temporary variable to iterate over it, instead of iterating over activewindow.select..

Thanks again for your help.