(WF4) Showing an InArgument as a CheckBox in the Workflow Designer Property Grid

Here’s a workflow designer FAQ finally getting the attention it deserves, and helping show off how to do custom property editing in designer.

“How do I show an InArgument<bool>/<enum> in the property grid as a checkbox/combobox?”

 

[Aside: Before we start the guide, note that actually doing the thing you are asking about has certain implications, and that there’s one obvious alternative that works in some cases: using a plain property, instead of an InArgument<T> typed-property .

Once you go the InArgument<T> route and restrict the UI to just a checkbox, the person using your custom activity can’t set an InArgument<bool> to custom expressions through the property grid. Which might be useful. But they can still set it to a custom expression by other ways, like editing XAML, if they can access the XAML. And they might choose to set no value at all, i.e. their InArgument<bool> == null. Take all of this into account when designing your custom activity logic.]

 

OK, here goes.

We have a custom activity. Via the property grid it must be possible to set the EnableLions argument to one of exactly two expressions: ‘true’, or ‘false’, using a checkbox.

The first thing we should do in order to accomplish the task is associate the custom property editor to the property. We can do this in one of two ways.

Way #1) Real attribute on the property:

public class ZooActivity : CodeActivity

{

    [Editor(typeof(InArgumentBoolPropertyEditor), typeof(PropertyValueEditor))]

    public InArgument<bool> EnableLions { get; set; }

 

    protected override void Execute(CodeActivityContext context)

    {

        //...

    }

}

 

Way #2) registering metadata (someone will need to explicitly call this code):

void RegisterMetadata()

    {

        AttributeTableBuilder tb = new AttributeTableBuilder();

        tb.AddCustomAttributes(typeof(ZooActivity), "EnableLions",

            new EditorAttribute(

               typeof(InArgumentBoolPropertyEditor),

               typeof(PropertyValueEditor)));

        MetadataStore.AddAttributeTable(tb.CreateTable());

    }

 

Once this attribute applied, when you select the activity the workflow designer will try to instantiate an InArgumentBoolPropertyEditor object to act as the property editor. The property editor type is a new type defined thusly:

public class InArgumentBoolPropertyEditor

    : PropertyValueEditor

{

    public InArgumentBoolPropertyEditor()

    {

        ResourceDictionary resources = Application.Current.Resources;

        this.InlineEditorTemplate = (DataTemplate)

            resources["ArgumentBoolLiteralPropertyEditor"];

    }

}

 

Notes:

  • we’re creating an inline property editor, so we subclass PropertyValueEditor (in System.Activities.Presentation.PropertyEditing)
  • we must set InlineEditorTemplate, which should be a DataTemplate, but in practice can come from anywhere
  • for a rehosted app, an easy way to store your inline editor templates is in Application.xaml as application resources, which is convenient the demo here, but in practice you’ll probably need to store it elsewhere.

The resource loaded above is just a regular WPF DataTemplate, holding the checkbox.

<Application.Resources>

    <ResourceDictionary

        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:local="clr-namespace:MyConverterNamespace">

       <DataTemplate x:Key="ArgumentBoolLiteralPropertyEditor" >

            <DataTemplate.Resources>

                <local:InArgumentBoolConverter x:Key="InArgumentBoolConverter"/>

            </DataTemplate.Resources>

        <CheckBox

               IsChecked="{Binding Path=Value, Mode=TwoWay, Converter={StaticResource InArgumentBoolConverter}}"

               />

        </DataTemplate>

    </ResourceDictionary>

 

Notes:

  • In order to bind Checkbox.IsChecked to the property value Value, which is of type InArgument<bool>, we had to define a custom type converter class. It’s used as a StaticResource.
  • the DataContext which the DataTemplate is being bound to is an instance of ModelPropertyEntry (inherits PropertyEntry)
  • you can pass a ModelPropertyEntry to converters such as ModelPropertyEntryToModelItemConverter (System.Activities.Presentation.Converters), this is helpful for implementing some more advanced custom property editors

Last piece of the puzzle is the converter implementation.

public class InArgumentBoolConverter : IValueConverter

{

    public object Convert(

        object value,

        Type targetType,

        object parameter,

        System.Globalization.CultureInfo culture)

    {

        if (value is InArgument<bool>)

        {

            Activity<bool> expression = ((InArgument<bool>)value).Expression;

            if (expression is Literal<bool>)

            {

                return ((Literal<bool>)expression).Value;

            }

        }

 

        return null;

    }

 

    public object ConvertBack(

        object value,

        Type targetType,

        object parameter,

        System.Globalization.CultureInfo culture)

    {

        if (value is bool)

        {

            return new InArgument<bool>(new Literal<bool>((bool)value));

        }

        else

        {

            return null;

        }

    }

}

 

Notes:

  • We only understand how to convert the InArgument<bool> if it is set to a Literal<bool> expression. Otherwise the checkbox will show a gray mark (indeterminate)
  • If the InArgument<bool> value is null, we also return null and the checkbox state is indeterminate
  • Remember we’re binding to PropertyEntry.Value value, which is expected by our activity OM to be an object of type InArgument<bool>.If we return something else, nothing happens
  • Literal<T> can be used for booleans and enums, but doesn’t work for most complex types
  • We could also have gone with VisualBasicValue<bool>(“false”) and VisualBasicValue<true>(“true”) instead of Literals, but note that in this case we are responsible for parsing the VisualBasic string – there is no API to call which can figure out the value as bool for us.
  • For the ComboBox/ListBox scenario, things should be similar, except that we need to remain aware of the expected item type of the control, and write our converter accordingly.

See also:

For reference to binding Combo Boxes, or binding InArguments but on the custom activity designer instead of the property grid, the guide here (‘Binding a custom activity property to a designer control’) should be helpful.

Trivia:

There is one other property editing class which you can subclass and use with workflow designer: DialogPropertyValueEditor. It works similarly, a little differently. There is a sample on MSDN.