Thursday, 13 October 2011

Morphing Conditional Activities

I’m currently working on a workflow implementation which, like many, is going to be used by users who are not necessarily programmers. So the thought of giving them conditions to write in code is the kind of thing that keeps me up at night.

I’m using Flowchart as the root node as it provides the sort of UI that we need, however there are a few issues.

First off is the display of the FlowDecision node. The standard look and feel is as follows…

image

That’s all well and good, but I’m trying to keep this usable from non-programmers and so setting the Condition property (a) from the property grid and (b) in code is a step too far.

Re-Designing FlowDecision

So, the first step is to re-design this designer. It’s a simple job as with WF4 it’s a load easier to register a new designer. Here’s what I ended up with, the XAML and code is shown later…

image

That’s looking more user friendly already. The first step was to create a new designer, the XAML of which is shown below (I’ve taken some formatting liberties with the XAML to fit on the page)…

  <sap:ActivityDesigner x:Class="CustomActivities.CustomFlowDecisionDesigner"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap
="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv
="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">

<
sap:WorkflowItemPresenter Item="{Binding ModelItem.Condition}"
HintText
="Drop a condition here"/>

</
sap:ActivityDesigner
>

All I’ve done here is derive from ActivityDesigner and then create a WorkflowItemPresenter (there’s a bit more work to do on this but I’ll save that for another blog post).


So, now I want to have a ‘condition’ activity (that’s a naming convention I’ve come up with myself). A condition activity returns a boolean, so will fit nicely into the slot provided by FlowDecision. My first one is the “IsAPastyMuncher” activity (I’m from Cornwall in the UK, pasties are part of my heritage – for some great ones you can do a lot worse than order over the phone from Malcolm Barnecutts).


Anyhow, this activity returns True or False, and the default designer looks like this…


image


Now that’s OK when it’s on its own, but as this activity is designed to be dropped within the FlowDecision it doesn’t look as good in that instance…


image


Removing the ‘Chrome’


There’s a bit too much ‘chrome’ going on there for my liking – the reason being that a standard designer derives from ActivityDesigner which adds the adornments such as the border, name and so on. What I’d really like is ‘chrome-less’ experience in the designer, and to do that all you need to do is change the base class of your designer from ActivityDesigner to WorkflowViewElement. This is as simple as editing the XAML directly and altering the root element to the following…

<sap:WorkflowViewElement x:Class="CustomActivities.IsAPastyMuncherDesigner"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap
="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv
="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">

<
CheckBox Content="Munches Pasties" IsChecked="{Binding ModelItem
.MunchesPasties}"/>

</
sap:WorkflowViewElement
>

Then you’ll have a much better experience within the designer…


image


That’s looking a load better – maybe still some room for improvement mind you (again another post I must get around to) but it’s getting there. But now to the main event…


Morphing Activities


There is however one major problem with the IsAPastyMuncher activity as now, when added to the toolbox & dropped outside of a decision I see just the following…


image


Now that’s a bit too minimal even for me! What I would ideally like to do is check if this activity has been dropped inside a FlowDecision, and if not automatically surround it with one. Your first thought might be to dive in with an activity that implements IActivityTemplateFactory, create a FlowDecision and within it create the IsAPastyMuncher activity. There’s one problem with that – FlowDecision doesn’t derive from Activity and cannot therefore be returned from the custom factory.


Never fear however, where there’s a will there’s a way, and instead I’ve overridden a method within the IsAPastyMuncher activity that is called when the activity is created (i.e. by the drag & drop operation) and in here I can do the switch. The code is shown below…

  protected override void OnModelItemChanged(object newItem)
{
ConditionHelper.MorphIntoDecision(newItem);
}

I’ve created the ConditionHelper class which actually performs the swap…

internal class ConditionHelper
{
/// <summary>
///
Method that morphs a dropped activity inside a flow step into the same
///
activity but within a flow decision
/// </summary>
/// <param name="newItem"></param>
public static void MorphIntoDecision(object newItem)
{
if (newItem is ModelItem)
{
ModelItem item = newItem as ModelItem;
ModelItem parent = item.Parent;

if (parent.ItemType == typeof(FlowStep))
{
// Morph the parent to a flow decision
EditingContext context = item.GetEditingContext();
ModelItem newModelItem = ModelFactory.CreateItem(context, new FlowDecision());

using (ModelEditingScope scope = newModelItem.BeginEdit("Convert to Decision"))
{
MorphHelper.MorphObject(parent, newModelItem);
MorphHelper.MorphProperties(parent, newModelItem);

// Now I need to set newModelItem.Condition = parent.Action,
// so the MorphProperties helper is pretty useless in this instance!
ModelProperty conditionProp = newModelItem.Properties["Condition"];
ModelProperty actionProp = parent.Properties["Action"];
conditionProp.SetValue(actionProp.Value);
actionProp.SetValue(null);

scope.Complete();
}
}
}
}
}

When an activity other than FlowDecision is added to the flow chart activity it is surrounded with a FlowStep activity. I check if this is the case and if so construct a FlowDecision activity as the parent instead and morph the existing parent into the new activity (well, MorphHelper.MorphObject does this for me). Any properties on the original parent activity which match with property names on the new parent activity are copied across using the MorphHelper.MorphProperties call, but then I need to move the actual activity (i.e. the IsAPastyMuncher) from the FlowStep to the FlowDecision. On FlowStep the property holding the activity is Action, whereas on FlowDecision this is Condition, so I use the ModelProperty class to make this change.


Now I can drop on an IsAPastyMuncher activity and have it automatically surrounded with a FlowDecision, but also allow the user to drop one inside a FlowDecision that’s already on the design surface which is the best of both worlds.


The Code


If you would like to download a sample please click here. This is built on Visual Studio 2010.


In the example there are 2 custom activities on the toolbox – the IsAPastyMuncher which morphs inside a FlowDecision when dropped, and the LovesClottedCream activity which does not.

9 comments:

Unknown said...

Hey this looks like something i could use.

Do your decision activity support a false and true branch?

Will it be possible to do that same for flowswitch?

The link to your sample seems to be broken :(

Could you mail me the sample?
- rck at mysupply dot dk

In advance thanks! :)

Unknown said...
This comment has been removed by the author.
Morgan said...

Travelling at the moment, will dig out the code when I get back home.

Biondo said...

Very interesting post! I need to do the same in my workflow application. I want to modify the designer of FlowDecision activity in order to allow users to define conditions through a user friendly GUI.
Download link seems to be broken, could you email me the source code? r.vviani AT libero DOT it.
Thanks.

Morgan said...

Emails sent!

Eugen Schwarzgorn said...

Could you mail me the sample please?
Thanks in advance!

ulker said...

Hi Morgan,

It's very useful for me too. I'm trying to make a custom decision activity in my application. I'll be grateful if you send me the sample.

Thanks in advance.

Hagashen Naidu said...

HI,

Are you able to make the code available again or can you please email it to me?

Thanks

Phu Cuong Bui said...

Very interesting post! I need to do the same in my workflow application. Could you mail me the sample please?

Thanks!
Cuong Bui