But it turns out that on the list of improvement we shipped with VSSDK 1.1, we only talked about the print preview, and not the nested shape feature.
This post aims at answering what has changed, and why we did not advertise this feature too much. Since at least one person is wondering, it's worth reviewing the improvement:
- the improvement is, in fact, in the runtime of the DSL Tools. And this runtime, you might know it, is shipped with the Visual Studio 2008 SP1, not the VSSK. The part of the DSL Tools which is in the VSSDK covering what we call the "authoring experience", that is developing a DSL. This means we did not add anything to the Dsl designer itself to improve the support of nested shapes: you will still need to add custom code to use of this improved feature.
- you could already create nested shapes before SP1, using custom code as proposed in the book, for instance. The book actually proposes two ways of achieving this. The first one is the right but until the VS 2008 SP1, there were problems with:
- the routing of the connectors
- the collapsing of shapes
- The last news is, you'll still need to write code to use nested shapes, but this code will be simpler since the fact of moving the nested shapes is done for you, and the nested connector routing is much better now.
In this post, I'll show:
- what the problem was with the nested shapes before the fix
- what is better
- how you can profit from this feature
- what custom code you can add depending on the additional behaviours you want to get,
What was the problem with nested shapes before SP1 ; what is improved.
In pre SP1 DSL Tools:
- you needed to create a lot of custom code
- you had, anyway, the connector routing problem:
The following picture shows what this problem was: the connectors could cross the nested shapes, as if they had not been there.
Now, with the post SP1 fix, you'll get the following behaviour as far as the connectors routing is concerned.
How to create a DSL using nested shapes
Remains now to learn how to create a DSL using nested shapes.
- For this, starting with a Minimal Language template. create a DSL named NestedShapesSample: You get this domain model
- Transform the domain model in the following way:
- Add a domain class named Compound, and change its property Inheritance Modifier property to abstract.
- Make the ExampleModel inherit from Compound
- Make the ExampleElement inherit from Compound.
- Remove the ExampleModelHasElements relationship between ExampleModel and ExampleElement and replace it by an embedding between Compound and ExampleElement
- Rename the ExampleElements role of ExampleElement in this relation to SubElements, and the Compound role to Parent
- At that point if you try to save your designer definition, you should get an error : "The Parent Element Path for Shape Map from ExampleElement to ExampleShape must not be null.". this is because we did not re-map the ExampleShape to the ExmapleElement after removing the ExempleModelHasElements relationship and replacing it by CompoundHasSubElements.
In order to get it right, instead of giving the Parent element path, you'll have to check the Has Custom Parent Shape checkbox, and write one line of custom code.
Note that, If you had followed your intuition, you would probably had writen "CompoundHasSubElements.Parent/!Compound" in the Parent Element Path text box, but, in fact, the authoring still does not let you write that (you would end-up with "A GeometryShape may not be parented on a GeometryShape. In the ShapeMap mapping ExampleElement to ExampleShape, the parent element path leads to Compound, which has a subclass ExampleElement whose mapped shape is ExampleShape" ). This is because we did not finish the authoring part of the behaviour.
- Save your DesignerDefinition.dsl file (there should not be any error now)
- Transform all templates and try to compile the Dsl assembly,
You'll get a compile error this time, because you need to produce the GetParentForExampleElement method for as custom code.
- Add the following custom code:
- In the Dsl project create a new folder and name it CustomCode
- In this folder, Add a class named FixUpDiagram. Turn it partial, and do not forget to remove the last segment of the namespace(CustomCode)
- the code of CustomCode\FixUpDiagram.cs should be the following:
partial class FixUpDiagram
private ModelElement GetParentForExampleElement(ExampleElement exampleElement)
- Build your DSL, and start it (with or without Debugging): you can now create an ExampleElement either on the diagram, or in an existing ExampleElement
Be sure to give a big size to one of your ExampleElement so that you can drop new ExampleElement in it, because, for the moment, the size of the parent ExampleElement will not increase. To have such a behaviour, we'll need a little bit more custom code. This will be the object of the third part.
Adding more Custom code to the shape, to improve the behaviour
With the simple modification we made the behaviour of our DSL is not yet perfect. The layout of the connectors is right, but we would like:
- either to expand the size of the parent shape when we add a new child shape or move it inside its parent shape: in this way the children shape would be prisoner of its parent.
- or to let the children shapes move out their parent shapes but then re-parent them.
As a function of the wanted behaviour, the custom code is different.
First case: Constraining children shapes into their parent's shape
In order to constraint the children shapes to remain in their parent shape we need to override a few methods and properties in the ExampleShape class.
- allow the children to resize their parent. When they move next to the border of their parent shape, the size of the parent shape will increase. This is enabled by overriding the AllowsChildrenToResizeParent property.
- prevent the parent shape from reducing to a size that would move its children shapes out of its boundaries. this is enabled by overriding the MinimumResizableSize property to a value that contains all the children. There already is a method that computes this size; CalculateMinimumSizeBasedOnChildren()
The custom code is the following:
partial class ExampleShape
/// Cannot move the children outside their children shape
public override bool AllowsChildrenToResizeParent
/// Cannot size down the parent shape if the children shapes were to move out of their
/// parent's boundary
public override SizeD MinimumResizableSize
Second case: Reparenting an ExampleElement depending on its position
The alternative is to let the shapes move in and out the parent shapes, but change their parent depending on the geometry. The custom code is, in that case, different. This will be the subject of a next post if some of you are interested by this behaviour.
Having collapsing shape reduce their size
The last customisation I want to talk about is the fact of having collapsing shapes. To let you ExampleShapes be collapsible, you need:
- To add an Expand / Collapse decorator on the ExampleShape, and Transform all templates.
If you do only that, when clicking on the collapse decorator the nested shapes and connectors are hidden, but the size of the parent shape does not change.
- If you are interested in having this size reduced when collapsing the shape, simply add the following custom code, still in the same CustomCode\ExampleShape.cs file
partial class ExampleShape
/// Keeps the size of the expanded bounds of the shape
protected RectangleD ExpandedBounds;
/// When we collapse the shape, we keep its current bound and reduce its size
protected override void Collapse()
this.ExpandedBounds = this.Bounds;
this.Bounds = this.AbsoluteBounds;
this.AbsoluteBounds = new RectangleD(this.Location, new SizeD(1.5, 0.3));
/// When we expand the shape, we restore the expanded bounds
protected override void Expand()
this.Bounds = this.ExpandedBounds;
After collapsing the "Started" shape, you get this
Note that we do not see any longer the connectors form started to terminated. They were hidden as well. This behaviour might suit you or not, but this is beyond the scope of this post.
In this post we went through the nested shapes support in VS 2008 SP1 / VSSDK 1.1.
If some of you are interested we could go further in a future post presenting custom code that brings other behaviours such as:
- moving a shape outside its parent reparents the child
- collapsing a shape containing connectors from its children to the outside of the collapsed shape would show "synthesis" connector.