DSL Tools #2 - Preventing Invalid Connections
Considering the model described in the previous post, I have a connector that allows the user to define the flow between FlowElements.
From the domain model, you can see that any given FlowElement can connect to any other FlowElement (any number in fact). A FlowElement - this is important - can be one of these concrete domain classes: Start, End, ActivityElement, or GatewayElement.
Although I modeled it this way - to simplify a number of things - there are certain rules that need to be enforced:
- Start cannot be the target of any link.
- End cannot be the source of any link.
- You can cannot link two gateways.
- You cannot link any element to itself.
Implementing these validations again requires custom code. But it's quite simple.
1. Assumptions
If you have a reference relation in you model (Flow in the sample) then you need a Connector in place to define the corresponding diagram element. I won't get into this since it's quite well described in the documentation.
Then you will also need a ConnectionTool defined in the Editor Toolbox Tabs designer. In my sample this tool is called FlowTool.
The interesting thing now is that for each ConnectionTool you will see a class generated (in ConnectionBuilders.cs file) that defines that tool's behavior. This class will have a name in the following form: <ToolName>ConnectAction (FlowToolConectAction in the sample).
2. Create a partial class for that FlowToolConnectAction
This generated class enforces the behavior required to implement our specific validation requirements. We'll need to extend it.
/// <summary>
/// Flow tool connection type.
/// </summary>
private partial class FlowToolConnectionType
{
// ...
}
3. Override the CanCreateConnection function
CanCreateConnection is called whenever the user creates a link with the corresponding connection tool and moves the mouse over any element in the model.
All we need is to inspect which are the source and target elements and return false in those cases where we don't want to allow the connection to be created.
Here's the code to implement our 4 special requirements:
/// <summary>
/// Determines if a connection can be created between two elements.
/// </summary>
/// <param name="sourceShapeElement">Source element of the
/// connection.</param>
/// <param name="targetShapeElement">Target element of the
/// connection.</param>
/// <param name="connectionWarning"></param>
/// <returns>A boolean that indicates if a connection can be created
/// between two elements.</returns>
public override bool CanCreateConnection(ShapeElement
sourceShapeElement, ShapeElement targetShapeElement,
ref string connectionWarning)
{
// Validations
if ((sourceShapeElement != null) && (targetShapeElement != null))
{
// No element can link to itself
if (sourceShapeElement == targetShapeElement)
{
connectionWarning =
Properties.Resources.CannotAddFlowToSelf;
return false;
}
// Validations if the Target is a Start Point
Start startTarget = targetShapeElement.ModelElement as Start;
if (startTarget != null)
{
// Start cannot be the target of any link
connectionWarning =
Properties.Resources.CannotFlowToStart;
return false;
}
// Validations if the Source is an End Point
End endSource = sourceShapeElement.ModelElement as End;
if (endSource != null)
{
// End cannot be the source of any link
connectionWarning =
Properties.Resources.CannotFlowFromEnd;
return false;
}
// Validations if the Source is a Gateway
GatewayElement gatewayElementSource =
(sourceShapeElement.ModelElement as GatewayElement);
if (gatewayElementSource != null)
{
// Cannot link two gateways
if ((targetShapeElement.ModelElement as GatewayElement)
!= null)
{
connectionWarning =
Properties.Resources.CannotAddFlowBetweenGateways;
return false;
}
}
}
// Default behavior
return base.CanCreateConnection(sourceShapeElement,
targetShapeElement, ref connectionWarning);
}
There are a few extra things worth mentioning:
- Be sure to invoke base.CanCreateConnection in the end of the function.
- The connectionWarning parameter allows you to provide feedback to user on why the connection is not permitted. It will be presented as a small tooltip in the designer.
- Notice also that the source and target elements are passed in as shapes. You can implement the validations using shapes or using model elements (as I did).