[Update (2008-05-21): This code is now hosted at CodePlex as part of JaDAL. And a follow-up article was published.]
This article is part of a series. The table of contents can be found at the end of the first article. In that article you can also find a brief overview. In the second part there is a short user guide and a download link of the source code and binaries.
How does it all work?
I don’t want to explain every single detail of my code. If you want to go that deep, you have to read the source code yourself and maybe you will find some comments. I will only show you the fundamental parts of the CompartmentMapping library.
There are a few things you have to consider to achieve the aim of the library:
- When creating a connector from Shape A to Shape B how should I get and store the Compartment Entry information for the Reference Relationship?
- After that, the connector representing this relationship must be drawn in such a way that it seems to be a connector from one entry to another.
- What can a user do to destroy the layout of my connectors? And even more important: how can I prevent him from doing so?
- If the user deletes a compartment entry that is part of a relationship, the relationship needs to be deleted, too.
The Connection Builder
There is the concept of Connection Builders used by the DSL Tools. While your day to day use of DSL, you don’t have to worry about Connection Builders since they will be generated by the DSL Tools code generator. But you can turn this generation off and provide your implementation of a Connection Builder for a certain relationship. (see number 5 in the user guide).
A Connection Builder is a static class and will be assigned to a connection toolbar item of your editor. This class contains four interesting methods:
bool CanAcceptSourceAndTarget(ModelElement, ModelElement)
ElementLink Connect(ModelElement, ModelElement)
The first two methods are used to determine if a particular
ModelElement can act as source or target of you connection. For example you can check the
ModelElement for a certain property to be set and only allow elements with this property as source and with another property as target, or whatever.
CanAcceptSourceAndTarget() you can check whenever a concrete combination of source and target
ModelElements is allowed to be connected by your relationship.
Last but not least with the
Connect() method you have to create this new relationship for the two selected
In such a Connection Builder I will add the logic to check not only the model elements but also the selected entry inside the Compartment Shape. At this point I will mix the model representation (containing of Domain Classes and Relationships) and the graphical appearance (containing of Shapes and Connectors) but there is no better way at this time since the DSL Tools can only create connectors from shape to shape.
The first problem I ran into: How to get the shape of the model element in the Connection Builder if the only parameter is the
ModelElement? I used the PresentationViewsSubject.GetPresentation() method and I’m hoping it will always work. The architecture is build in such a way, that one
ModelElement can have multiple shapes, but I didn’t saw such a configuration until now, so I assume there will be only one shape.
After holding the shape in my hand I need to know something about the selected Compartment Entry. This becomes a little bit complicated, too: Sometimes I need the entry right below the mouse cursor (for
CanAcceptSource()) and sometimes the entry that was below the mouse when the user pressed the mouse button. Of course a Compartment Shape doesn’t provide any of that information. Take a look at
ICompartmentMouseActionTrackable in the CompartmentMapping library and you will see in which way I handle mouse events of the shape to track all these mouse actions for the Compartment Shapes that are under the control of my library.
With all these new information my
CompartmentMappingBuilderBase can decide to allow or permit the creation of a connection. With some
virtual methods, it can delegate more details of this decision to your code (see the advanced options here).
How to trick the routing algorithm of DSL Tools
With the Connection Builder one can create relationships in the domain model but the connector inside the visual representation of your model will still be routed from shape to shape and won’t give you a proper understanding of the entry to entry relationship. So we have to change the routing in a way that the start and endpoints of the connector will be glued near to the entry on one side of the shape.
This will be done with an AddRule (
CompartmentMappingAddRuleBase) every time a new connector will be added to the diagram (and when opening a saved diagram).
After determination the favored points on the shape outline you can set them to the connector with the FromEndPoint and ToEndPoint properties. Don’t forget to change the FixedFrom and FixedTo to
VGFixedCode.Caller as well.
Don’t get tricked by the routing algorithm
If you change the start and endpoints of a connector in the AddRule these values aren’t set forever. The user could collapse and expand the compartment shape or use the "Reroute" command that is visible in the context menu of every connector. He could also move the connector or only the start and endpoints on the shape outline. If he deletes one entry that was shown above an entry with a connection this entry moves closer to the top and the connection should do the same.
With a few lines of code, we can forbid the user to change the routing of a connectors. In the connector class the
CanManuallyRoute property simply have to return
Every time the size of the shape changes (see OnAbsoluteBoundsChanged event) I will recalculate the connection points of all connectors assigned to this shape. This event will handle a bunch of cases for me: insertion and deletion of other entries, expanding and collapsing of the whole shape and singe compartment lists and renaming of entries which can cause reordering of the compartment list.
The delete propagation is an easy requirement. I just added a DeletingRule that keep track of the deletion of certain Compartment Entries and if one is deleted it looks for Compartment Mapping Relationships and deletes them.
It is a little bit challenging to find the relationships coming from the entry. If you want to know more details take a look at the
CompartmentEntryDeletingRuleBase source code.
In the last article of this series I will explain the way of removing the "Reroute" command from the connector context menu. The actual code consist only of a few lines, but the way I found the point to do it can be interesting for someone because the "Reroute" command is not proper documented anywhere.