Binding data to Combobox in custom activity design

2019-04-21 20:46发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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.