Connectors between compartment shape entries with DSL Tools – part 3
Tags: dotnet, dsl, JaDAL, orcas, visual studio, vsxAdd comments
[Update (2008-05-21): This code is now hosted at CodePlex as part of JaDAL. And a follow-up article was published.]
Previously on…
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 CanAcceptSource(ModelElement)
-
bool CanAcceptTarget(ModelElement)
-
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.
With 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 ModelElements
.
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 CompartmentMouseTrack
and 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 false
.
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.
Delete propagation
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.
Upcoming article
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.
March 1st, 2008 at 10:32 pm
[…] Part 3 and part 4 of this series will give an in depth view into the internals of the library. […]
May 21st, 2008 at 3:15 am
[…] read the second part! The second part contains obsolete information that will be corrected here. Part 3 and part 4 describe the inside of the library and they are still valid for the new […]
May 28th, 2008 at 12:12 am
[…] 5. Connectors between compartment shape entries with DSL Tools – part 3 […]