Let’s start with a disclaimer here:
First, this is the result of experimenting and watching the Microsoft dll’s with Redgate’s Reflector. This means the code is for demonstartion purposes only and is not at all production ready.
Second, the FreeFoemActivityDesigner is a huge subject. This post describes some scenario’s of what you can do with it but is by no means complete. As I have an idea for using this designer, the subjects handled are choosen with that idea in mind. There are a lot of other possibilities which I will not cover and to whome I don’t know the answer.
Third, because of point two, if you have any questions: I probably don’t know the answer!! But you can ask them anyway.
So, let’s get started…
Adding, Removing and Editing contained Activities
The FreeFormActivityDesigner has in its inheritance tree the CompositeActivityDesigner. This means it is meant to be used for activities deriving from CompositeActivity and thus you can
add other activities to it which will result in addng other ActivityDesigners to your FreeFormActivityDesigner derived class.
The most important methods for managing the contained activities are:
public virtual bool CanInsertActivities(HitTestInfo insertLocation,
ReadOnlyCollection<Activity> activitiesToInsert);
public virtual bool CanMoveActivities(HitTestInfo moveLocation,
orums
ReadOnlyCollection<Activity> activitiesToMove);
public virtual bool CanRemoveActivities(
ReadOnlyCollection<Activity> activitiesToRemove);
protected virtual void OnContainedActivitiesChanged(
ActivityCollectionChangeEventArgs listChangeArgs);
protected virtual void OnContainedActivitiesChanging(
ActivityCollectionChangeEventArgs listChangeArgs);
protected virtual void OnContainedActivityChanged(
ActivityChangedEventArgs e);
public virtual void InsertActivities(HitTestInfo insertLocation,
ReadOnlyCollection<Activity> activitiesToInsert);
public virtual void MoveActivities(HitTestInfo moveLocation,
ReadOnlyCollection<Activity> activitiesToMove);
public virtual void RemoveActivities(ReadOnlyCollection<Activity> activitiesToRemove);
The methods starting with “Can” and returning a boolean allow you to specify if activities can be acted upon in the composite activity being designed. What action is clear form the name of the method. Mind however that the Move is “Move between designers” and NOT “Move within a designer”.
However, don’t let these methods and their names fool you: they are there for your use and the framework only uses them in two cases:
The first case is the CanRemoveActivities method which gets called if you have an ActivityDesigner selected inside your FreeFormActivityDesigner inherited object and you press the Delete key. If you return false from the method call, your selected activity will not get removed.
The second case is the CanInsertActivities method which only gets called if you Paste an activity in your FreeFormActivityDesigner inherited object.
Bug or Feature?
You’d think that implementing these methods would offer you some benefit during editing of the composite activity but you’d be wrong.
If you want to prevent an activity from being inserted you have to override the OnDragOver method and call the CanInsertActivities from there. Based on the result you’d set the Effect property of the ActivityDragEventArgs argument to Move|Copy or None if insertion is allowed or not.
What I wonder is why Microsoft didn’t implement this behaviuor in there DragDropManager messagefilter.
Same goes for the CanMoveActivities. This method is supposed to signal weather activities can be moved from one designer to another. Again you’d think it would be used during drag/drop manipulations but it isn’t. You’re again on your own to implement this functionality.So one wonders: is this a bug or a feature. And if it is a feature, then what can be gained by it?
The “On” prefixed methods are more or less self-documenting: the OnContainedActivities methods get called when the contained activities collection is changing or changed. The OnContainedActivityChanged is called when a contained activity is changed. Mind the nuance: the OnContainedActivities methods are called when the collection of contained activities changes, the OnContainedActivityChanged when an activity itself is changed. This last also means changing a property, moving the activity etc…
The last action oriented methods are called when effectively performing the action. The MoveActivities method is called when moving an activity from another designer inside your designer and NOT when moving an activity from your designer into another designer. Infact, in this last case not even the RemoveActivities method is called!!!
Making Connections
The FreeFormActivityDesigner allows you to make connections as you want it, in contrast with for example the Microsoft supplied SequentialActivityDesigner which makes the connections for you. In the last one, there is a default vertical line representing the connection between the start and the end, and you drop activities on this vertical line.
With the FreeFormActivityDesigner you can drop Activities anywhere you want, and they do not get connected by default. Then if you hover over the activity, connectionponts are made visible. Pressing the left mousebutton on such a point and dragging the mouse creates a connection which you can then attach to another activity.
But what is really happening under the hood, and how can you interfere with it?
There are two stages in making connections between activities:
- A graphical stage inside the FreeFormActiviyDesigner in which a graphical representation is created of the connection and in which it is also
decided if a connection can be made. More on this last further - A “business” stage in which the activities are connected. Allthough there is no built-in support to decide if a connection can be made, you should of course also validate this here, or (actually “and”) in the ActivityValidator inherited class for you activity.
Making connections between activity designers
When you press the left mousebutton on an activities connectionpoint and start moving the mouse (keeping the button pressed), deep down in the bowels of the workflow design experience the CreateConnector method of the FreeFormActivityDesigner to which the activity is a child is called with the ConnectionPoint data of the connection point pressed.
You are now given the chance to return your own custom Connector derived class. If for some reason the source connection point is not allowed to function as a source, you simply return null from this method.
When you continue to move the mouse, two methods are continouosly polled:
- The CanConnectContainedDesigners method of your FreeFormActivityDesigner derived class
- The CanConnect of your ActivityDesigner derived class
These two give you the chance to tell the designer environment if the connection between the activities can be made. If you return true, then the connector will snap to the target connection point and if you return false nothing will happen.
Connection points
As stated above, connections are made between connection points and your desicion if connections can be made is based on these connection points.
The question is of course how to specify and identify connection points.
Specifying connection points is also a two stage process:
- First, you specify the coordinates of the points on the designers edge which can be used as connection points by overriding the GetConnections(DesignerEdges edges) member of the ActivityDesigner class
- Next, you specify which connection points will be made visible when the user hovers the mouse over the ActivityDesigner by overriding the GetConnectionPoints(DesignerEdges edges) member
Identifying connection points is done on a designer-edge basis. As you can see from the signature of the above memtioned members, connection points are asked by specifying the edge along which they reside. Mind you however that this DesignerEdges enumeration has the FlagsAttribute attribute, so combinations of it’s members are allowed. Thus, you typically will have code like this:
if((edges & DesignerEdges.Top) == DesignerEdges.Top)
{
// Add your points for the top here
}
if ((edges & DesignerEdges.Bottom) == DesignerEdges.Bottom)
{
// Add your points for the bottom here
}
The second property which identifies the connection points is it’s connection index. It’s what? It’s “connection index”. Remember you specified the possible points on the designer edge that could be used as conneciton points. Remember also that you returned these on an DesignerEdge basis and in a collection. Now this “connection index” is the index in this collection to which the connection points map. Or visually:
So, to get the point coordinates mapping to the provided ConnectionPoint, you get the collection of points for the corresponding DesignerEdge, that is ConnectionEdge, and retrieve the point by index in the collection using ConnectionIndex:
Dictionary<DesignerEdges, List<Point>> pointsByEdge;
ConnectionPoint c;
Point p = pointsByEdge[c.ConnectionEdge][c.ConnectionIndex];
Making connections between activities
Of course, all this makes no sense if these connections somehow eventually don’t show up in tree of activities to execute.
To manage this tree you have these methods to override in the FreeFormActivityDesigner:
protected virtual void OnConnectorAdded (ConnectorEventArgs e)
protected internal virtual void OnConnectorChanged (ConnectorEventArgs e)
protected virtual void OnConnectorAdded (ConnectorEventArgs e)
protected internal virtual void OnContainedDesignersConnected (
ConnectionPoint source,
ConnectionPoint target
)
And this single method in the ActivityDesigner:
protected virtual void OnConnected (
ConnectionPoint source,
ConnectionPoint target
)
What you do inside these methods is of course dependent on the type of CompositeActivity you are designing. But generaly you will somehow “connect”/”disconnect” the two activities uner design. How you do this is entirely up to you.
Downloads
In the accompening code download you will find some sample code mostly demonstrating making connection.
Links
FreeformActivityDesigner and Connectors question…
Designer wish list?
FlowChartworkflow – Custom FreeformActivityDesigner sample