Visio 2010 Validation Rules (part 1)

The Microsoft Visio team recently blogged about Creating custom validation rules for Visio 2010 (see http://blogs.msdn.com/visio/archive/2009/09/10/creating-custom-validation-rules-for-visio-2010.aspx), in which it was suggested that we can use Visual Basic for Applications to interact with the new validation API.  I thought, there’s a challenge … so I will develop a VBA project to do just that.  I will upload the code soon, but, as it is only half developed today, I will demonstrate how to understand why a rule has been broken.

In the following BPMN diagram, I have failed to connect a Task to the End Event.  I have made it obvious here but it could be easy to overlook in a larger diagram.  In fact, I know of one company who were advised by a “consultant” that there must be something wrong when the end of a connector goes red therefore you should move it away from the shape until it goes green!  The result was that the company had over 300 flow diagrams without a single valid connection!  That company is no more!

image

The new UI in Visio 2010 has a new Process tab, which has a button to Check Diagram and a tick box display the Issues Window.  This shows that two rules have been broken, but it does not give you any more than the rule Description and Category.  Now, you may assume that Microsoft have the BPMN rules modelled correctly, but if you are creating your own rule sets, then you need to understand exactly why they have been broken, therefore I have constructed a basic VBA dialog that displays the Issues and Rules in a selected document.

Issues

So, when an Issue is read from the new document.Validation.Issues collection

Issue 1 has the rule with the description An End Event must have incoming Sequence Flow. has the name NoSequenceFlowToEndEvent , and the category of End Events .

Issue 2 has the rule with the description The flow must have a source and target. has a name NoTargetOrSource, and a category list of Sequence Flow, Message Flow .

 

image

Rules

The Name, Description and Category of the Rule, cannot be sufficient to define what the rule is, so we must explore the Rules of the active RuleSet in the document.  In this case it is the BPMN RuleSet, which contains many Rules, and using my dialogue, I can find the Rule with the name NoTargetOrSource to see what it’s definition is.

image

Now we can begin to understand how a rule is defined by inspecting the Test Expression and Filter Expression values.

TestExpression=AND(AGGCOUNT(GLUEDSHAPES(4))=1,AGGCOUNT(GLUEDSHAPES(5))=1)

FilterExpression=OR(Actions.MessageFlow.Checked, Actions.SequenceFlow.Checked)

These expressions look very similar to ShapeSheet formulae, but they are not actual ShapeSheet formulae.  However, some ShapeSheet functions can be used in these expressions, but Microsoft have had to create some extra quasi-ShapeSheet functions that are needed in order to create a rule definition.

Closer inspection of the FilterExpression shows that this rule is only to be applied to shapes which have either an Actions.MessageFlow or Actions.SequenceFlow row checked.  The Dynamic Connector in the BPMN template has been modified to have such rows in the Actions section of the spreadsheet.

image

If you now look at the TestExpression reveals that the new GluedShapes method, as seen in my previous post Listing Connections in Visio 2010 ( http://bvisual.spaces.live.com/blog/cns!3350D61BC93733A9!1820.entry ), returns an array of glued shapes .  The arguments 4 and 5 are the actual values of the constants visGluedShapesIncoming2D and visGluedShapesOutgoing2D respectively.  The function AGGCOUNT() obviously means the count the number of items in the retuned array, so the whole expression simply says that there must be a connected 2D shape at either end of the connector.

The other broken rule, NoSequenceFlowToEndEvent, has :

TestExpression=AGGCOUNT(FILTERSET(GLUEDSHAPES(1),"Actions.SequenceFlow.Checked"))>0

FilterExpression=AND(HASCATEGORY("Event"),Actions.End.Checked)

Inspection of these expressions reveal that it applies to and shape that has the category “Event”, and has the row Actions.End checked, and it must have a glued connector which has the row Actions.SequenceFlow checked.  I discussed the new ShapeSheet function HASCATEGORY() in my earlier blog Visio 2010 : Containment and Cross-Functional Flowcharts ( http://bvisual.spaces.live.com/blog/cns!3350D61BC93733A9!1811.entry ), but the other new function is FILTERSET() obviously means that the returned array of GluedShapes is filtered to include only those that satisfy the second argument, in this case, Actions.SequenceFlow.Checked.

Once you start examining the rules, you can begin to understand how you can create your own ones.  Getting interested?

Listing Connections in Visio 2010

One of the best bits of Visio is the connections between shapes, but it has always been difficult to understand these connections in code.  There are connections to and from shapes, which require you to understand where you are and which way to look … at the begining or the end of a 1D connector. Visio 2010 has added some useful extra methods to the shape object which makes understanding connections much easier.  This post will show you how you can use the new GluedShapes() and ConnectedShapes() methods.

Take, for example, a network diagram where there are connections between servers and routers:

image

You may wish to list the connections at the start and end of each cable.  Previously, this would have meant inspecting the connected cell to see if was at the beginning or end of the line, but now you can use the GluedShapes() method of a shape to retrieve an array of the 2D shapes connected at one end or another with the the relevant arguments, visGluedShapesIncoming2D or visGluedShapesOutgoing2D.  The ListGluedConnections macro below displays the following in the immediate window:

Connector     Dynamic connector
              >             Router.45     router-02
              <             Server        server-01
Connector     Dynamic connector.107
              >             Router.45     router-02
              <             Server.30     server-02
Connector     Dynamic connector.108
              >             Router        router-01
              <             Server.75     server-03
Connector     Dynamic connector.109
              >             Router.91     router-03
              <             Server.30     server-02
Connector     Dynamic connector.110
              >             Router.91     router-03
              <             Server        server-01

Public Sub ListGluedConnections()
Dim shp As Visio.Shape
Dim connectorShape As Visio.Shape
Dim sourceShape As Visio.Shape
Dim targetShape As Visio.Shape
Dim aryTargetIDs() As Long
Dim arySourceIDs() As Long
Dim targetID As Long
Dim sourceID As Long
Dim i As Integer

For Each shp In Visio.ActivePage.Shapes
    If shp.OneD Then
        Debug.Print "Connector", shp.Name
        arySourceIDs = shp.GluedShapes(visGluedShapesIncoming2D, "")
        For i = 0 To UBound(arySourceIDs)
            Set sourceShape = Visio.ActivePage.Shapes.ItemFromID(arySourceIDs(i))
            If sourceShape.CellExists("Prop.NetworkName", Visio.visExistsAnywhere) Then
                Debug.Print , ">", sourceShape.Name, sourceShape.Cells("Prop.NetworkName").ResultStr("")
            End If
        Next
        aryTargetIDs = shp.GluedShapes(visGluedShapesOutgoing2D, "")
        For i = 0 To UBound(aryTargetIDs)
            Set targetShape = Visio.ActivePage.Shapes.ItemFromID(aryTargetIDs(i))
            If targetShape.CellExists("Prop.NetworkName", Visio.visExistsAnywhere) Then
                Debug.Print , "<", targetShape.Name, targetShape.Cells("Prop.NetworkName").ResultStr("")
            End If
        Next
    End If
Next

End Sub

Similarly, you may want to simply list the next connected 2D shape, effectively ignoring the cable.  In this case, you can use the new ConnectedShapes() method, with the relevant arguments visGluedShapesIncoming2D or visGluedShapesOutgoing2D, to produce an output like the following ( using the ListNextConnections macro below):

Shape         Server        server-01
              >             Router.45     router-02
              >             Router.91     router-03
Shape         Router        router-01
              <             Server.75     server-03
Shape         Server.30     server-02
              >             Router.45     router-02
              >             Router.91     router-03
Shape         Router.45     router-02
              <             Server        server-01
              <             Server.30     server-02
Shape         Server.75     server-03
              >             Router        router-01
Shape         Router.91     router-03
              <             Server        server-01
              <             Server.30     server-02

Public Sub ListNextConnections()
Dim shp As Visio.Shape
Dim connectorShape As Visio.Shape
Dim sourceShape As Visio.Shape
Dim targetShape As Visio.Shape
Dim aryTargetIDs() As Long
Dim arySourceIDs() As Long
Dim targetID As Long
Dim sourceID As Long
Dim i As Integer

For Each shp In Visio.ActivePage.Shapes
    If Not shp.OneD Then
        If shp.CellExists("Prop.NetworkName", Visio.visExistsAnywhere) Then
            Debug.Print "Shape", shp.Name, shp.Cells("Prop.NetworkName").ResultStr("")
            arySourceIDs = shp.ConnectedShapes(visConnectedShapesOutgoingNodes, "")
            For i = 0 To UBound(arySourceIDs)
                Set sourceShape = Visio.ActivePage.Shapes.ItemFromID(arySourceIDs(i))
                If sourceShape.CellExists("Prop.NetworkName", Visio.visExistsAnywhere) Then
                    Debug.Print , "<", sourceShape.Name, sourceShape.Cells("Prop.NetworkName").ResultStr("")
                End If
            Next
            aryTargetIDs = shp.ConnectedShapes(visConnectedShapesIncomingNodes, "")
            For i = 0 To UBound(aryTargetIDs)
                Set targetShape = Visio.ActivePage.Shapes.ItemFromID(aryTargetIDs(i))
                If targetShape.CellExists("Prop.NetworkName", Visio.visExistsAnywhere) Then
                    Debug.Print , ">", targetShape.Name, targetShape.Cells("Prop.NetworkName").ResultStr("")
                End If
            Next
        End If
    End If
Next

End Sub

I think this makes interrogation of connected diagrams, of all flavours, much simpler, though I would love to have similar ShapeSheet functions too!

JackBinnall

O365 and SharePoint

Simplify Tasks

Want to learn the simple way?

Paul Turley's SQL Server BI Blog

sharing my experiences with the Microsoft data platform, SQL Server BI, Data Modeling, SSAS Design, Power Pivot, Power BI, SSRS Advanced Design, Power BI, Dashboards & Visualization since 2009

John Goldsmith's visLog

be smart, be clear, be visual ...

Mo's blog

Personal views on Dynamics 365 for Operations and Technical Architecture.

Chris Webb's BI Blog

Microsoft Power BI, Analysis Services, MDX, DAX, M, Power Pivot and Power Query

davecra.wordpress.com/

Azure Solutions for Office 365, and more...

Rob Fahrni

I AM FAHRNI

johnvisiomvp

Life with Visio and other Microsoft Toys!

Nilsandrey's Weblog

Just another WordPress.com weblog

Things that Should be Easy

Every so often (too often in the IT industry) I encounter things that should have been very easy to do but turned out to be far too complicated. My favorite topics include SharePoint, .Net development, and software architecture, especially distributed systems.

Visio Guy

Smart graphics for visual people