Visio Guy

Visio Discussions => Shapes & Templates => Topic started by: doudou on July 16, 2014, 04:45:00 AM

Title: Quick way to reconnect many Shapes
Post by: doudou on July 16, 2014, 04:45:00 AM
Hello,

I am very new to Visio so I apologize in advance if my question is silly. I have a Visio 2010 diagram with many shapes created by my predecessor. There are many instance of shapes that "appear" to be connected but are not really connected: there is a connector between the shapes but when I select the connector I only see one bright green circle at the end of the connector instead of two (one at each end). I am guessing my predecessor did not extend the connector long enough for the shapes to connect. My question is, is there a quick way in Visio 2010 or 2013 to reconnect all the shapes that should have been connected without the need to go reconnect one pair at a time. Thanks in advance!

Jean
Title: Re: Quick way to reconnect many Shapes
Post by: Yacine on July 16, 2014, 04:10:21 PM
You'll either have to do the job by hand, or chose the wiser solution namely to accept the imperfection of your drawings.

A macro could probably do the job by checking the spatialneighborhood of the connectors, but I think this is beyond the capabilities of a visio beginner.
Title: Re: Quick way to reconnect many Shapes
Post by: doudou on July 16, 2014, 05:34:57 PM
Hi Yacine,

Thank you for reply. Although I have very limited knowledge of  Visio and its object model. I have a fair amount of experience with VBA in Excel and Outlook. I will  give Shape.SpatialNeighbors property a shot. I think i will basically loop through all shapes on the page, look for connectors in close vicinity, check if they are connected, if not connect them. Thanks again

Jean
Title: Re: Quick way to reconnect many Shapes
Post by: Yacine on July 16, 2014, 07:10:10 PM
Bonne chance, je reste à ta disposition ;-)

PS: just an idea, but if the neighborhood of the whole connector does not work, may be a temporarly placed shape at the end of connector may give more precise data.

Second thought, you may want to connect the connector not the whole shape, but to its shape handles, in order to avoid too much re-routing. Recording an according macro shall give you the right commands.
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on July 16, 2014, 10:28:24 PM
Another possibility, assuming that the un-connected ends are either on or very close to the desired shape would be to simply go thru and wiggle each shape by a teeny bit.  That ought to be enough to get the connector to glue.  Final spot of each shape would be its starting position.

Might work.

Wapperdude.
Title: Re: Quick way to reconnect many Shapes
Post by: Yacine on July 16, 2014, 10:32:47 PM
@Wayne, "... and wiggle each shape by a teeny bit..." by hand or otherwise? Didn't really get your idea.
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on July 16, 2014, 11:35:10 PM
By code...so each shape gets moved a small amount +/1 x-direction, +/- y-direction. 

The assumption is that all of these disconnected ends are either on top of the shape's connection point, but not glued, or all are consistently away from the shape by a small amount.  Each shape is moved to allow the offset connection point to glue to the shape, and shape returned.  Or, if connector and connection point overlap, but are not glued, then, returning the shape to that spot will activate the gluing mechanism.  For shapes already glued, no harm done. 

Seemed like it might be easier than to search the neighborhood, try to find who's disconnected and then take steps to connect.  Not saying it'll work.  But, it might be easier.  Ought to be easy to write a small loop to nudge the shape around by a specific amount.

Clearly not elegant, and if there's no consistency, might not work.  But, if there is consistency, could be simple, quick approach.

Wapperdude
Title: Re: Quick way to reconnect many Shapes
Post by: doudou on July 17, 2014, 04:22:17 AM
@ Wapperdude. Thanks for your suggestion. It is actually pretty elegant and would have worked perfect for my situation. However it appears that the "Selection.Move" method does not behave the same way as mouse click-hold move action. In the sense that the snapping of a connector to a shape appears to be a mouse-click event and not triggered by "moving" of shapes. We can also confirm this by selecting a connector and moving it with the keyboard arrows. I have actually the VBA code shown below. It appears to work for small test cases, but I I am getting some really weird stuff when I try it on the real file.

Sub FixConnections()

    Dim viShapes As Visio.Shapes
    Set viShapes = ActivePage.Shapes
    Dim myShape As Visio.Shape, con As Visio.Shape
    Dim neighbor As Visio.Selection
    Dim vsoCell1 As Visio.Cell
    Dim vsoCell2 As Visio.Cell
   
   
    For Each myShape In viShapes
        If Not myShape.CellExists("EndX", 0) Then
            Set neighbor = myShape.SpatialNeighbors(8, 0.025, 0)
            For Each con In neighbor
                If con.CellExists("EndX", 0) Then
                    If con.Cells("BeginX").result(visInches) > myShape.Cells("PinX").result(visInches) Then
                        Set vsoCell1 = Application.ActiveWindow.Page.Shapes.ItemFromID(con.ID).CellsU("BeginX")
                        Set vsoCell2 = Application.ActiveWindow.Page.Shapes.ItemFromID(myShape.ID).CellsSRC(7, 1, 0)
                        If Not CheckConnected(myShape, con) Then
                            vsoCell1.GlueTo vsoCell2
                        End If
                    ElseIf con.Cells("EndX").result(visInches) < myShape.Cells("PinX").result(visInches) Then
                        Set vsoCell1 = Application.ActiveWindow.Page.Shapes.ItemFromID(con.ID).CellsU("EndX")
                        Set vsoCell2 = Application.ActiveWindow.Page.Shapes.ItemFromID(myShape.ID).CellsSRC(7, 0, 0)
                        If Not CheckConnected(myShape, con) Then
                            vsoCell1.GlueTo vsoCell2
                        End If
                    End If
                End If
            Next
        End If
    Next
End Sub

Function CheckConnected(myShape As Visio.Shape, con As Visio.Shape) As Boolean
    Dim IDs() As Long
    IDs = myShape.ConnectedShapes(visConnectedShapesAllNodes, "")
    Dim i As Long
    CheckConnected = False
    For i = 0 To UBound(IDs, 1)
        If IDs(i) = con.ID Then
            CheckConnected = True
            Exit For
        End If
    Next i
End Function
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on July 17, 2014, 05:53:10 AM
The code appears to be OK.  Not my forte.  However, it appears that you could simplify the code a little, perhaps that'll help with some of problems you're having on the full up version.

1.)  The "IFs" checking for connectivity don't seem to be necessary.  Execution is fast, and if already connected, who cares? 
2.)  Once you eliminate the "IFs", then you can eliminate CheckConnected code.

Of course, not seeing your complete drawing, I could be overlooking something, but, the code will only apply to the selected connector and shape as dictated by the SRC description.

Anyway, nice work so far.  Looks like you were able to put the code together fairly quickly.

Wapperdude

Title: Re: Quick way to reconnect many Shapes
Post by: Yacine on July 17, 2014, 07:45:54 AM
Hi Jean,
I played a little bit with your code.
I changed the cell to connect to to (1,1,0)
I increased the tolerance of the spatialneighbors
and it seems to work

(had to write my own connectedshapes function though, cause I'm still on V2007.
Title: Re: Quick way to reconnect many Shapes
Post by: AndyW on July 17, 2014, 10:26:46 AM
Wiggling the shape only works if done manually, not by automation.
Title: Re: Quick way to reconnect many Shapes
Post by: doudou on July 17, 2014, 04:10:16 PM
@Yacine. c'est gentil de ta part d'avoir pris le temps de m'aider! Merci bien!

In English:   ;D

Changing the cell to connect to to (1,1,0) seems to work on the real diagram!. Although in some way the connectors shape changed.  I think the best way would be to have the connector glue to the closest shape handle instead of a fixed cell. I am not sure if this is possible with Visio though (please be patient with me, first time I used Visio was 8 days ago!)
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on July 17, 2014, 05:02:10 PM
The difference in approaches is that the (1,1,0) invokes the "walking glue" method.  That means as the shape is moved, Visio will decide where it glues to the shape.  This could be a problem if you always wanted a specific location.  It also explains why the connector shape changes...it's gluing to a different spot.

The (7,1,0) approach glues to a specific connector point.  The 2nd number tells which connector point to use.  Not having seen your document, your code always uses either the 1st or 2nd row, i.e., "0" or "1".  If the shape has additional connection points, and one or more of them is the desired, then, it is necessary to change the row entry value.  This is a limitation of using specific connection points.  This complicates the problem, because now you have to determine which connector should go to which connection point.  I suspect that the neighborhood needs to be small enough to only surround a given connection point on a shape, but large enough to capture the connector floating end.  That should allow unique identities for both connector shape ID, and the specific connection point row.  Thus, the code would have to step thru each connection point on a shape, verify if it's not connected, find the nearest connector, and then glue the two together.

HTH
Wapperdude
Title: Re: Quick way to reconnect many Shapes
Post by: doudou on July 17, 2014, 06:12:58 PM
@wapperdude, "Thus, the code would have to step thru each connection point on a shape, verify if it's not connected, find the nearest connector, and then glue the two together." Can you please post a snippet that I could use. I think this the solution to the problem. Thanks!
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on July 17, 2014, 09:18:59 PM
Like Yacine, I only have V2007.  Unlike Yacine, my programming is not that strong, but, I think you have most of the pieces.

You need to iterate thru each shape that is not a connector. 
    For each shape, you need to go thru each connection point.
       Set a variable for number of rows and then step thru.  E.g.
           nrows = shp.RowCount(visSectionConnectionPts)
           For j = 0 To nrows - 1
               Add code to grab X, Y positions of connection point
               translate positions to page coordinates
               use those coordinates for neighbhood search for connector
               check connectivity
                    if not, then use shape IDs, and row index "j" to glue connector to connection point.
          next row.
      next shape

HTH
You'll probably do this faster than I could.
Wapperdude
Title: Re: Quick way to reconnect many Shapes
Post by: doudou on September 03, 2014, 02:01:23 PM
Quote from: wapperdude on July 17, 2014, 09:18:59 PM
Like Yacine, I only have V2007.  Unlike Yacine, my programming is not that strong, but, I think you have most of the pieces.

You need to iterate thru each shape that is not a connector. 
    For each shape, you need to go thru each connection point.
       Set a variable for number of rows and then step thru.  E.g.
           nrows = shp.RowCount(visSectionConnectionPts)
           For j = 0 To nrows - 1
               Add code to grab X, Y positions of connection point
               translate positions to page coordinates
               use those coordinates for neighbhood search for connector
               check connectivity
                    if not, then use shape IDs, and row index "j" to glue connector to connection point.
          next row.
      next shape

HTH
You'll probably do this faster than I could.
Wapperdude

Hello again wapperdude, I am sorry for my late response. I had to stop working on this project at work for another urgent project that I came up. I started playing with this last night. I can't figure out how to code "use those coordinates for neighbhood search for connector". Perhaps because I haven't used Visio in over a month :). I have below the code. Any help would be much appreciated. Thanks.


Public Sub FixConnectionsNew() 'New Version that loops through all connection points
    Dim viShapes As Visio.Shapes
    Set viShapes = ActivePage.Shapes
    Dim myShape As Visio.Shape
    Dim vsoCell1 As Visio.Cell, vsoCell2 As Visio.Cell
    Dim xCoord As Double, yCoord As Double
    Dim nrows As Long, rowNum As Long
    For Each myShape In viShapes
        nrows = myShape.RowCount(Visio.visSectionConnectionPts)
        If nrows > 0 Then ' Check for a 2D shape
            For rowNum = 0 To nrows - 1
                GetConnectionPointsCoord myShape, xCoord, yCoord, rowNum
                'use those coordinates for neighbhood search for connector
                'check connectivity
                'if not, then use shape IDs, and row index to glue connector to connection point
            Next rowNum
        End If
    Next
End Sub


Public Sub GetConnectionPointsCoord(shpObj As Visio.Shape, ByRef xCoord As Double, ByRef yCoord As Double, rowNum As Long) 'Gets the absolute coordinates for a connection point

    Dim ShapeX As Double, ShapeY As Double
   
    ShapeX = shpObj.CellsSRC(Visio.visSectionObject, visRowXFormOut, visXFormPinX).Result(Visio.visNone)
    ShapeX = ShapeX - shpObj.CellsSRC(Visio.visSectionObject, visRowXFormOut, visXFormLocPinX).Result(Visio.visNone)
    ShapeY = shpObj.CellsSRC(Visio.visSectionObject, visRowXFormOut, visXFormPinY).Result(Visio.visNone)
    ShapeY = ShapeY - shpObj.CellsSRC(Visio.visSectionObject, visRowXFormOut, visXFormLocPinY).Result(Visio.visNone)
   
    xCoord = ShapeX + shpObj.CellsSRC(Visio.visSectionConnectionPts, rowNum, visX).Result(Visio.visNone)
    yCoord = ShapeY + shpObj.CellsSRC(Visio.visSectionConnectionPts, rowNum, visY).Result(Visio.visNone)

End Sub

Function CheckConnected(myShape As Visio.Shape, con As Visio.Shape) As Boolean 'Checks if two shapes are connected
    Dim IDs() As Long
    IDs = myShape.ConnectedShapes(visConnectedShapesAllNodes, "")
    Dim i As Long
    CheckConnected = False
    For i = 0 To UBound(IDs, 1)
        If IDs(i) = con.ID Then
            CheckConnected = True
            Exit For
        End If
    Next i
End Function
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on September 06, 2014, 10:32:42 PM
Sorry for the delay in responding.

First, it would seem that the quickest way to get a connection point location, assuming that LocPinX and LocPinY are set to 1/2 width and 1/2 height., would be to either add or subtract appropriate amounts from the PinX and PinY values.  For example if row_1 was Width*0, Height*0.5, then, the connection point has page coordinates =PinX-width*0.5 and PinY.   Also assumes that shape is not part of a group.

Finding coordinates of the connector is matter of looking at BeginX,Y and EndX,Y.  If an end is connected, the entries will be formulas, otherwise, they are values.  Compare values to see which match.

I've attached a simple, but crude code that finds 1D shapes that have either only one or no connections.  Don't care if both ends connected.  If neither end is connected, it will not be part of the connects object, so that cannot be a search criteria.  Once an appropriate 1D shape found, there's a msgbox, then, program does a 2nd loop thru all the shapes (clunky), to find what shapes touch the connector.  Assumes there's touching going on.  Another msgbox.

At this point, need code development to (a) find end points of the 1D shape, and then (b) search thru the touching shape to see which connection point has matching X & Y coordinates.  This then provides necessary information to fill in the glueto formulas.

HTH
Wapperdude
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on September 07, 2014, 01:23:30 AM
Updated code.  Assumes that connector ends and connections points touch.  Code will glue all connectors to coinciding connection points.

Wapperdude
Title: Re: Quick way to reconnect many Shapes
Post by: doudou on September 07, 2014, 05:38:09 PM
Quote from: wapperdude on September 07, 2014, 01:23:30 AM
Updated code.  Assumes that connector ends and connections points touch.  Code will glue all connectors to coinciding connection points.

Wapperdude

Thanks again for your invaluable help and thorough answers, this works great. However, I think it will be great if we can make a little more general, in the sense that the 1D shape and 2D shape don't just need to be touching to get glued but also if the connection point on the 2D shape is within a certain tolerance level (set by the user) from the starting or ending point on the 1D. I think we only need to modify the following conditional statement in your code with another condition.

If intReturnValue = visSpatialTouching Then


thanks again

Jean
Title: Re: Quick way to reconnect many Shapes
Post by: wapperdude on September 07, 2014, 06:48:39 PM
I opted for the touching requirement for two reasons:
  1.  It was the easiest to implement.   ::)
  2.  For non-touching situations, there might be more than one equally possible solution.  If that were ever to occur, then it would really be better for user intervention rather than arbitrary machine decision.  It would be relatively simple to determine if the 1D shape is not touching anything at either end, and then, perhaps, change its color to make it stand out.  The user could then manually intervene.

Anyway, I think there are enough pieces in the code for you to embellish as desired.  Google Visio, vba, spatialneighbors for additional info.

Wapperdude
Title: Re: Quick way to reconnect many Shapes
Post by: Thomas Winkel on July 20, 2015, 12:19:05 PM
Hello friends,

thank you for this useful discussions and coding.
Based on this work I created the following code:


Sub reconnect()
    Dim cons As Visio.Selection
    Dim con As Visio.Shape
    Dim shp As Visio.Shape
    Dim sel As Visio.Selection
    Dim xBeg As Double, xEnd As Double, yBeg As Double, yEnd As Double
    Dim xS As Double, yS As Double, xP As Double, yP As Double
    Dim i As Integer
   
    Const dXY = 0.1
    Const tolerance = 0.001
   
    Set cons = ActivePage.CreateSelection(visSelTypeByRole, visSelModeSkipSuper, visRoleSelConnector)
   
    For Each con In cons
        If con.Connects.Count < 2 Then
            xBeg = con.CellsU("BeginX")
            yBeg = con.CellsU("BeginY")
            xEnd = con.CellsU("EndX")
            yEnd = con.CellsU("EndY")
           
            Set sel = con.SpatialNeighbors(visSpatialTouching, tolerance, 0)
           
            For Each shp In sel
                For i = 0 To shp.RowCount(visSectionConnectionPts) - 1
                    xS = shp.CellsSRC(visSectionConnectionPts, i, visCnnctX)
                    yS = shp.CellsSRC(visSectionConnectionPts, i, visCnnctY)
                    shp.XYToPage xS, yS, xP, yP
                   
                    If (Abs(xP - xBeg) < dXY) And (Abs(yP - yBeg) < dXY) Then
                        con.CellsU("BeginX").GlueTo shp.CellsSRC(visSectionConnectionPts, i, 0)
                    End If
                    If (Abs(xP - xEnd) < dXY) And (Abs(yP - yEnd) < dXY) Then
                        con.CellsU("EndX").GlueTo shp.CellsSRC(visSectionConnectionPts, i, 0)
                    End If
                Next i
            Next shp
        End If
    Next con
End Sub


Hints: