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
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.
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
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.
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.
@Wayne, "... and wiggle each shape by a teeny bit..." by hand or otherwise? Didn't really get your idea.
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
@ 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
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
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.
Wiggling the shape only works if done manually, not by automation.
@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!)
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
@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!
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
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
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
Updated code. Assumes that connector ends and connections points touch. Code will glue all connectors to coinciding connection points.
Wapperdude
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
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
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:
- Use XYToPage to transform the local connection point coordinates to page coordinates, so also flipped or rotated shapes are supported.
- Iterate only through unconnected connector shapes.
- Connection points in sub-shapes are not considered.