Workflow Activities and Data Templates
A.K.A. “Why won’t the workflow designer use my data template?”
I was on site with a customer today writing some custom activities and found a problem that wasn’t immediately obvious how I could get around it (and indeed it’s taken me some hours of head scratching and debugging to finally find the answer).
I have a custom activity on which I’ve defined the following property…
public Comparison Comparison { get; set; }
The comparison class is one I’ve made up myself, and I have derived classes such as BooleanComparison, StringComparison and so on. The actual value of the Comparison property is set by my designer. In the designer I then want to show the appropriate UI for each type of designer, so I created some data templates…
<DataTemplate DataType="{x:Type helpers:Comparison}"> <TextBlock Text="BOOL"/> </DataTemplate> <DataTemplate DataType="{x:Type helpers:StringComparison}"> <TextBlock Text="STRING"/> </DataTemplate> <DataTemplate DataType="{x:Type helpers:DateTimeComparison}"> <TextBlock Text="DATETIME"/> </DataTemplate>
OK, so these are hardly going to set the world on fire, but you get the idea. I then databound the Content property to a ContentPresenter so…
<ContentControl Content="{Binding ModelItem.Comparison}"/>
But on screen this never worked – I kept on getting the TypeName show up – a sure sign that the right data template wasn’t being selected. Puzzled, I fiddled around for some hours trying to solve this. I thought I must have the XAML wrong, so popped the lot into a new WPF project but it all worked as expected. Even more puzzling. I then decided to move the property into the designer, which changed the binding from ModelItem.Comparison to just Comparison, and lo & behold it now worked.
Knowing a bit about how ModelItem worked under the covers (it’s a custom type converter) I twigged – the object returned from ModelItem.Comparison wasn’t a real Comparison object (or rather, it was wrapped in something, which just so happened to be the ModelItem class).
So, armed with this I knew I was just about there, and a quick stint in Reflector told me I should call the GetCurrentValue() method of ModelItem in order to get the actual value for my binding. So I crufted up the following converter and now all is sweet in Workflow land for me – all I have to do is add on the converter to my binding and it’ll then find the appropriate data template.
<ContentControl Content="{Binding ModelItem.Comparison, Converter={StaticResource modelItemConverter}}" />
The converter class is shown below – it’s trivial to implement but this will save you typing a few lines!
public class ModelItemConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { object retVal = value; if (value is ModelItem) retVal = ((ModelItem)value).GetCurrentValue(); return retVal; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } }
Hopefully this will save someone some time!
Comments
Anonymous
June 24, 2011
Hi, I just stumbled across your blog, and I'm enjoying it. I do have a tip for this one, you could use the framework class System.Activities.Presentation.Converters.ModelToObjectValueConverter, which does the same as ModelItemConverter. TimAnonymous
June 24, 2011
Great - thanks for this Tim, I should have looked for this before constructing my own. I'll use the inbuilt one in the future - as an old colleague of mine used to say, "less code more smiles". Cheers, Morgan