In this post I wanted to highlight an interesting use of the Window method SelectionForDragCopy to get hold of the shapes at an intersection between two containers. The most useful scenario for this is the intersection between a Phase column and Function row in a Cross Functional Flowchart.
Understanding the Selection object
Before I get started, I’ll just consider what a Selection object is and where it comes from.
I think it’s common for people to think of the selection as referring to the shapes that are ‘selected’ in a Visio window.
For example, based on this screenshot:
…ActiveWindow.Selection would return three shapes and if you inspected the PrimaryItem property of that Selection, you’d get the ‘Assess readiness and priority’ process shape (which you can identify visually from its thicker blue border).
While it’s true that a selection is often paired with what’s actually selected in the UI, the object can also survive quite happily on its own and can really be thought of a special Visio collection type that includes a number of handy properties and methods.
Aside from being able to get hold of a Selection from a Window, the Page object also has a CreateSelection method that returns a Selection. This method allows you to pass in a selection type enum which will filter shapes accordingly:
- visSelTypeAll - A selection that initially contains all shapes
- visSelTypeByDataGraphic - A selection that initially contains all shapes that have a given type of data graphic applied
- visSelTypeByLayer - A selection that initially contains all the shapes of a given layer
- visSelTypeByMaster - A selection that initially contains all the instantiated shapes of a given master
- visSelTypeByRole - A selection that initially contains all the shapes of a given role [Connector, Container or Callout]
- visSelTypeByType - A selection that initially contains all the shapes of a given type [Bitmap, Group, Guide, Ink, Metafile, OLE or Shape]
- visSelTypeEmpty - A selection that initially contains no shapes
- visSelTypeSingle - A selection that initially contains one shape
[Note – in addition to Page, the CreateSelection method is also available on Master and Shape, allowing you to create a selection from shapes within those respective hosting objects.]
Once you’ve got hold of your selection you can then pass it around and manipulate it as required, crucially, without affecting the visually selected shapes in the ActiveWindow.
So that’s the Selection object, but what about the this SelectionForDragCopy property?
SelectionForDragCopy
The SelectionForDragCopy property gives you a convenient way of getting hold of the container member shapes that are ‘implicitly’ selected even though, in reality, they might not be. An example of this is where you drag a container shape around in a page and its member shapes come along for the ride. Think of the flowchart shapes that move in conjunction with their corresponding swimlane when you drag it to a different position.
Under the covers, I guess what this is doing, is calling the ContainerProperties.GetMemberShapes method. This returns an array of shapes IDs and this is then converted back into shape objects, which are finally added back to the selection.
Writing your own code to do this is fine of course, but SelectionForDragCopy offers similar functionality in a more convenient format as it returns shape objects (in a Selection) rather than an array of IDs.
There are, of course, trade offs here, but imagine a scenario where you want to perform an operation on a set of shapes that are at the intersect of a particular phase and swimlane in the a cross functional flowchart (see green area below).
Using the SelectionForDragCopy property you can create two selections that include the member shapes from each of the two (phase and swimlane) container shapes.
A quick approach might look something like this:
1: void Main()
2: {
3: var vApp = MyExtensions.GetRunningVisio();
4: var vPag = vApp.ActivePage;
5:
6: var initialSelection = vApp.ActiveWindow.Selection;
7: if (initialSelection.Count == 2)
8: {
9: var firstSelection = vPag.CreateSelection(
10: Visio.VisSelectionTypes.visSelTypeSingle,
11: Visio.VisSelectMode.visSelModeSkipSuper,
12: initialSelection[1]).SelectionForDragCopy;
13:
14: var secondSelection = vPag.CreateSelection(
15: Visio.VisSelectionTypes.visSelTypeSingle,
16: Visio.VisSelectMode.visSelModeSkipSuper,
17: initialSelection[2]).SelectionForDragCopy;
18:
19: var firstSeq = firstSelection.Cast<Visio.Shape>();
20: var secondSeq = secondSelection.Cast<Visio.Shape>();
21:
22: var intersectSeq = firstSeq.Intersect(secondSeq);
23:
24: var intersectResults =
25: from Visio.Shape shp in intersectSeq
26: where shp.Master.NameU == "Process"
27: orderby shp.CellsU["PinX"].ResultIU, shp.CellsU["PinY"].ResultIU
28: select shp.Characters.Text;
29:
30: intersectResults.Dump();
31: }
32: }
[Note – see this link for more details on MyExtensions.GetRunningVisio and using LinqPad: http://visualsignals.typepad.co.uk/vislog/2015/12/getting-started-with-c-in-linqpad-with-visio.html ]
There are three main stages to the above code:
- first is the creation of the separate selections
- second is getting the intersect of those two selections to create a sequence containing only shapes that appear in both selections
- the final part is to use LINQ to filter for the actual shapes of interest (‘Process’ instance shapes in this case)
So, if I zoom in a little on the previous image:
…the above code will produce the following output in LinqPad:
Parting thoughts
I think this is a handy tool for your Visio toolbox, but as the naming of the property suggests it’s not the original intended use, so be aware of this when you decide whether to use it in your own code. As I mentioned, the alternative would be to use the ContainerProperties.GetMemberShapes method and then do the same intersect on the shape IDs. This would get you to the same place albeit with a few extra lines of code.
These two methods allow you to get from the selected containers in to the member shapes, but don’t forget that you can also do the reverse as well using:
- Selection.MemberOfContainersIntersection
- Selection.MemberOfContainersUnion
which, from a selection of member shapes allow you to get out to the containing shapes instead.