Exploring Workflow Foundation Part 7: Custom Designers – The FreeformActivityDesigner class

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:

  1. 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
  2. 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:

  1. 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
  2. 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

Exploring Workflow Foundation Part 6.2: Custom Designers – The ActivityDesigner class and customization with drawing

While starting to experiment with the FreeFormActivityDesigner (about which the following post will handle), I did some further exploration of the ActivityDesigner base class and what can be done with custom drawing. That will be the subject of this post.

Overriding the OnPaint event

This one is rather simple and complex at the same time.

Overriding the OnPaint method gives you with the argument the chance to implement your own custom drawing functionality. There is however one caveat: the coordinates for positioning drawing elements on the screen are logical coordinates. Thus, the upper^left corner of your designer is not at coordinates (0,0) but at (ActivityDesigner.Bounds.X,ActivityDesigner.Bounds.Y)

So, to draw a black circle for your activity, you use following code:

protected override void OnPaint(ActivityDesignerPaintEventArgs e)
{
  e.Graphics.FillEllipse(new SolidBrush(Color.Black), Bounds);
}

Now, isn’t this simple? Yes it is.

Then where is the complexity? If you are knowledgeable with the Graphics object I suppose there is no complexity. But if you’re not, you have another subject to study.

Recap: Designer – Activity interaction

In the previous post I allready showed you how to get at the activity associated with the designer. Afterall: the “raison de vivre” of the ActivityDesigner is the Activity it represents.

When setting properties of the Activity, the designer framewotk also triggers a redraw of the associated ActivityDesigner and thus calls the OnPaint method. This allows you to update the visuals of your designer using the properties of the activity.

Glyphs: why do we need them

When starting to experiment with this custom drawing I also started wondering why one would need custom glyphs. The reason however is very simple: the Graphics object you receive in the OnPaint method only allows you to draw inside the rectangular Bounds defined box of the ActivityDesigner. Glyphs however allow you to draw outside of this box.

An application of this could be if you for example want to render connection ports on the boundary of your custom ActivityDesigner.

Glyphs are associated with an ActivityDesigner and can draw relative to this associated designer. You can see this by the signature of the methods:

public override System.Drawing.Rectangle GetBounds(ActivityDesigner designer, bool activated)
protected override void OnPaint(System.Drawing.Graphics graphics, 
  bool activated, 
  AmbientTheme ambientTheme, 
  ActivityDesigner designer)

As you can see from these signatures, they all have an ActivityDesigner parameter passed in. This is the designer associated with the glyph. It allows you to get it’s bounds and position your glyph relative to it.

Downloads

In the associated code you can find sample code for the above explanations. Hope you enjoy it.