Analysing Parent-Child Relations in Visio

This is the second part of the Developing for Visio series. Today, we’ll speak about how to analyse programmatically parent-child relationships in Visio diagrams. Often, especially in block diagrams there are boxes that contain other boxes.

sample diagram

However, during iterating through shapes in a Visio diagram such a relationship is not preserved.

foreach (Visio.Shape shape in this.visioControl.Window.Application.ActivePage.Shapes)
{
    MessageBox.Show(shape.Text);
}

This piece of code will output three shape names, as if the diagram was flat. If you read carefully the Visio object model, you can find that the Shape class contains two properties that might be of interest for us. These properties are Parent and ContainingShape. However, if you read documentation on them or try writing some code, you will realise they work only if shapes in a diagram are grouped. Indeed, grouping shapes when drawing Visio diagram isn’t a bad idea. But how often do you really group your shapes? How many diagram do you have with grouped shapes? I guess not many.

Thus, we have to find another way of analysing parent-child relationships in Visio diagrams. Fortunately, there is a property, SpatialNeighbors, of the Shape class that returns a collection of shapes that are in a given relation to a specified shape. Yes, this property does accept parameters. Actually, its signature is the following:

Selection Shape.SpatialNeighbors[short relation, double tolerance, short flags[, object resultRoot]]

relation specifies in which relation shapes should be to a specified one. Its value can be any of the VisSpatialRelationCode. At this moment we are only interested in two of them. The first one is visSpatialContainedIn that will return a shape that a the parent to a given one. For instance, if we run it for the “Financial Department” shape in the diagram above it will return “Acme Ltd”. The second property is visSpatialContain, it will return all the children of a specified shape, so if we pass “Acme Ltd”, then we will get “Financial Department” and “Marketing Department”.

tolerance specifies a distance between a given shape and shapes we are looking for. This parameter is useful if we want to find adjacent shapes, however if are looking for only child or parent shapes, we can leave it as zero.

flags sets properties of a returned result set, such as which shapes to return, in which order, etc. Its value can be one of the VisSpatialRelationFlags. Normally, I just set visSpatialFrontToBack.

resultRoot, MSDN says the following about it ‘If you don't specify ResultRoot, this property returns a Selection object that represents the shapes that meet certain criteria in relation to the specified shape. If you specify ResultRoot, this property returns a Selection object that represents all the shapes in the Shape object specified by ResultRoot that meet certain criteria in relation to the specified shape. For example, specify ResultRoot to find all shapes within a group that are near a specified shape.’ I normally leave this parameter blank.

The only thing that I haven’t mentioned about Shape.SpatialNeighbors is that it retuns an object of class Selection. If you just want to get a parent of a shape, then you can simply use Selection.PrimaryItem. Otherwise, when looking for children shapes you can simply iterate through Selection.

Okay, it’s time to write some code. We are going to create a simply application that displays a list of available shapes, like in the previous chapter, however this time it will preserve a shape hierarchy. I won’t cover the basics on how to create a Visio add-in or an application hosting a Visio control, since it was already covered in the previous chapter. In fact, I’ll just create a WPF application with an embedded Vision control.

Since, the Shape class has no notion of parent or child, the first thing we should do is to create a wrapper around that class that will have the Children property.

using Visio = Microsoft.Office.Interop.Visio;
//...
public class ShapeWrapper
{
    public Visio.Shape Shape { get; set; }

    private List<ShapeWrapper> children = new List<ShapeWrapper>();
    public List<ShapeWrapper> Children { get { return this.children; } }

    public ShapeWrapper(Visio.Shape shape)
    {
        Shape = shape;
    }
}

Good. Now, we should put down some code that will parse a diagram and preserve parent-child relations.

private void visioControl_DocumentOpened(object sender, EVisOcx_DocumentOpenedEvent e)
{
    //a list of already parsed shapes that are children to some other shapes
    List<Visio.Shape> addedShapes = new List<Visio.Shape>();
    //a list of wrappers that we are building
    List<ShapeWrapper> shapeWrappers = new List<ShapeWrapper>();

    foreach (Visio.Shape shape in this.visioControl.Window.Application.ActivePage.Shapes)
    {
        if (!addedShapes.Contains(shape))
        {
            ShapeWrapper shapeWrapper = new ShapeWrapper(shape);
            shapeWrappers.Add(shapeWrapper);

            //here we are trying to retrieve all the children of a current shape
            FindChildren(shapeWrapper, addedShapes);
        }
    }

    //just some WPF binding
    treeShapes.ItemsSource = shapeWrappers;
}

There are might be two confusing points about this piece of code. The first one is the mystical FindChildren() method, and the second on is the addedShapes collection. FindChildren() is given below, as for addedShapes, when we are iterating through all the shapes in a diagram we can encounter shapes that have been already added to a certain ShapeWrapper in shapeWrappers during the execution of the the FindChildren() method and it is much easier to add them to addedShapes and check afterwards rather than check recursively in shapeWrappers.

private void FindChildren(ShapeWrapper shapeWrapper, List<Visio.Shape> addedShapes)
{
    Visio.Selection children = shapeWrapper.Shape.SpatialNeighbors[(short)Visio.VisSpatialRelationCodes.visSpatialContain, 0,
                (short)Visio.VisSpatialRelationFlags.visSpatialFrontToBack];

    foreach (Visio.Shape child in children)
    {
        if (!addedShapes.Contains(child))
        {
            //MessageBox.Show(child.Text);
            ShapeWrapper childWrapper = new ShapeWrapper(child);
            shapeWrapper.Children.Add(childWrapper);

            FindChildren(childWrapper, addedShapes);
        }
    }
}

So here in the code above we use the aforementioned Shape.SpatialNeighbors.

After compiling the application you should see a diagram and a hierarchical list on the right.

result

This time we feature the architecture of Windows 2000 that I found on a Wikipedia page.

Source Code

I don’t provide the rest of the code related to XAML and Visio initialisation, instead you can download it here.

Mike Borozdin (Twitter)
6 September 2011

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way. My personal thoughts tend to change, hence the articles in this blog might not provide an accurate reflection of my present standpoint.

© Mike Borozdin