In first part of this serie we saw how to bind our designer properties to activity properties and create a custom designer for our activity. Let’s see how to bind to activity properties through the designer and allow user to enter values in the designer.

If you have some experience with WF Activities you know that input properties are of type InArgument so how do we convert and bind our regular types to a InArgument<T>?

Let’s go with simple types like String, supposing our SendTo activity has an Enabled property of type InArgument<bool> and we have a CheckBox on our designer. First approach would be to bind directly through ModelItem property of the designer, inherited from the base class by using a ValueConverter:

1
<CheckBox IsChecked="{Binding Path=ModelItem.IsEnabled, Mode=TwoWay, Converter={StaticResource ValueToInArgumentLiteralConverter}}" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ValueToInArgumentLiteralConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
var modelItem = value as ModelItem;
if (modelItem != null)
{
var expr = modelItem.Properties["Expression"];
if(expr != null)
{
return expr.Value;
}
}
}

return null;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;

var type = typeof (InArgument<>);
var genericArgument = type.MakeGenericType(value.GetType());
var argument = Activator.CreateInstance(genericArgument, value);

return argument;
}
}

This approach works fine but only for simple scenarios. If you want more control on selected properties, changing values, having dependent properties or if your designer UI is complex, I advise using a ViewModel instead. You can access current ModelItem from your view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface ISendToDesigner
{
ModelItem ModelItem { get; }
}

public class SendToViewModel
{
public virtual dynamic SelectedModelItem
{
get { return View != null ? View.ModelItem : null; }
}

public virtual bool IsEnabled
{
get { return isEnabled; }
set
{
isEnabled = value;
RaisePropertyChanged(() => IsEnabled);
UpdateModelProperties();
}
}

private void UpdateModelProperties()
{
SelectedModelItem.IsEnabled = IsEnabled.ToInArgument();
}
}

Did you see SelectedModelItem is of dynamic type? Exposing underlying ModelItem as dynamic allows us to directly assign properties without going through indexers. This trick results in much cleaner and better maintainable code, supposing you have unit tests for your view models! Also I’m using an extension method named ToInArgument which converts the primitive values to InArgument. Here’s the extension method:

1
2
3
4
public static InArgument<T> ToInArgument<T>(this T value)
{
return new InArgument<T>(value);
}

This works for primitive values but what if you have Enums, Guids, Lists or Dictionary properties on your Activity? Unfortunately though this seem to work, it won’t and designer shows an error. The alternative would be to use VisualBasicValue instead. If you are a C# developer, welcome yourself to the world of VB.NET!

Seems VisualBasicValue<T> can be assigned as InArgument<T> so you just need to convert your values to a VisualBasicValue. This approach works file with Enums as well, too bad you can not create an extension method with Enum constraint. Here’s an extension method that converts your enum to VisualBasicValue:

1
2
3
4
public static InArgument<T> ToExpression<T>(this T value)
{
return new VisualBasicValue<T>(string.Format("{0}.{1}", typeof(T).Name, value));
}

Now List and Dictionaries are a little bit different. Let’s see how I convert List<Guid> to a VisualBasicValue and you can use the same approach to make it work for all generic types:

1
2
3
4
5
6
7
8
public static InArgument<List<Guid>> ToExpression(this List<Guid> list)
{
var expr = "New List(Of Guid) From {{{0}}}";
var item = list.Select(i => string.Format("New Guid(\"{0}\")", i));
var items = string.Join(",", item);

return new VisualBasicValue<List<Guid>>(string.Format(expr, items));
}

On the next part of this post, we’ll see how to allow user to select existing variables (of a specific types) and import needed namespaces into our workflow xaml.