I have a custom activity, with a single in argument which is a string. However, rather than allow the designer to enter an arbitrary string, I want the designer to be presented with a Combobox with the list of options, the options are dynamic and are loaded from a database into a List<> collection.
My problem is I have no clue how to bind the Combobox in the designer to this list and have the selection set to the in argument of the activity. Visually I have the activity designer working, it is just this one step.
Normally, I would write the activity with a property
rather than an InArgument
. This simplifies the scenario:
<ComboBox ItemsSource="{Binding Path=ValidOptions}"
SelectedValue="{Binding Path=ModelItem.MyStringProperty, Mode=TwoWay}"/>
(here ValidOptions is some Collection property on your ActivityDesigner class. MyStringProperty is some public get/set/ property on the underlying activity such as:
public string MyStringProperty { get; set; }
)
The problem you will have if you add InArgument
to the mix is that the string values from the combo box cannot be directly assigned to a ModelItem
expecting InArgument<string>
. This is fixable using a custom IValueConverter
in your binding.
The previous answers are helpful but were not enough for me. Eventually I found a terrific article from 2012, in Microsoft's .Net 4.5 Developer Guide: Binding a custom activity property to a designer control. That article was almost the full answer - except for a minor bug in the custom converter class, and a major flaw: that technique will save a value from the ComboBox, but it will not restore it when you re-open your workflow.
Microsoft's Ron Jacobs has another answer for custom activity designers. I ended up combining the two to get a working solution.
Custom Designer
The ModelToObjectValueConverter
was an incredibly helpful resource, allowing me to skip creating my own IValueConverter
. In the ObjectDataProvider
you see me loading a list of strings by calling a static method, People.GetPeople()
. The ComboBox binds to that provider as the item source, but binds the selected value to the Person property on the custom Activity (below)
<sap:ActivityDesigner x:Class="ActivityLibrary1.ComboBoxActivityDesigner"
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:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
xmlns:c="clr-namespace:ActivityLibrary1">
<sap:ActivityDesigner.Resources>
<ResourceDictionary>
<sapc:ModelToObjectValueConverter x:Key="ModelToObjectValueConverter" />
<ObjectDataProvider x:Key="people" ObjectType="{x:Type c:People}" MethodName="GetPeople"/>
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<Grid>
<Label Content="Person" HorizontalAlignment="Left" VerticalAlignment="Top" />
<ComboBox HorizontalAlignment="Left"
Margin="66,0,0,0"
VerticalAlignment="Top"
Width="120"
SelectedValue="{Binding Path=ModelItem.Person, Mode=TwoWay, Converter={StaticResource ModelToObjectValueConverter} }"
ItemsSource="{Binding Source={StaticResource people}}">
</ComboBox>
</Grid>
</sap:ActivityDesigner>
Custom Code Activity
Note that this uses a property rather than an InArgument, which makes binding the ComboBox easier.
[Designer(typeof(ComboBoxActivityDesigner))]
public class CodeActivity1 : CodeActivity
{
public string Person { get; set; }
protected override void Execute(CodeActivityContext context)
{
// Just to demonstrate that it worked
MessageBox.Show(Person);
}
}
Workflow
Now the custom activity, CodeActivity1
, can be dragged onto a workflow. When you make a selection, the selected value will appear in the properties pane. Save the workflow. Close and re-open. The previously-selected value will persist as desired.
One way to solve it is to define your own ComboBoxEditor which derives from UITypeEditor.
Expose the collection you want to bind this combobox in the activity class and decorate your bindable property in Activity class with following attribute :
[EditorAttribute(typeof(CustomListBoxEditor), typeof(System.Drawing.Design.UITypeEditor))]
Also in the custom comboboxEditor you will have to modify your
EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) method to get the collection and bind it to the combobox.