I am having problems getting validation to work properly in the designer for my custom activity. The simplest sample to reproduce the behavior is as follows:
I have a custom WF4 activity with a dynamic collection of arguments stored in a dictionary:
[Designer(typeof(DictionaryActivityDesigner))]
public class DictionaryActivity : NativeActivity
{
[Browsable(false)]
public Dictionary<string, InArgument> Arguments { get; set; }
public InArgument<string> StringArg { get; set; }
public DictionaryActivity()
{
Arguments = new Dictionary<string, InArgument>();
}
protected override void Execute(NativeActivityContext context)
{ }
}
In the designer I dinamically create expression text boxes for editing these arguments. The user has the possibility to define the arguments and their types in a separate modal window, but for the sake of simplicity I have fixed the arguments in this sample:
public partial class DictionaryActivityDesigner
{
private Dictionary<string, Type> definition;
public DictionaryActivityDesigner()
{
definition = new Dictionary<string, Type>
{
{ "String Arg", typeof(string) },
{ "Int Arg", typeof(int) }
};
InitializeComponent();
}
public void InitializeGrid(Dictionary<string, Type> arguments)
{
ArgumentsGrid.RowDefinitions.Clear();
ArgumentsGrid.Children.Clear();
int gridRow = 0;
foreach (var arg in arguments)
{
ArgumentsGrid.RowDefinitions.Add(new RowDefinition());
var label = new Label()
{
Content = arg.Key + ":"
};
Grid.SetRow(label, gridRow);
Grid.SetColumn(label, 0);
ArgumentsGrid.Children.Add(label);
var textbox = new ExpressionTextBox()
{
ExpressionType = arg.Value,
OwnerActivity = ModelItem,
UseLocationExpression = false
};
var binding = new Binding()
{
Mode = BindingMode.TwoWay,
Converter = new ArgumentToExpressionConverter(),
ConverterParameter = "In",
Path = new PropertyPath("ModelItem.Arguments[(0)]", arg.Key)
};
textbox.SetBinding(ExpressionTextBox.ExpressionProperty, binding);
Grid.SetRow(textbox, gridRow);
Grid.SetColumn(textbox, 1);
ArgumentsGrid.Children.Add(textbox);
gridRow++;
}
}
private void ActivityDesigner_Loaded(object sender, RoutedEventArgs e)
{
InitializeGrid(definition);
}
}
Below is the XAML for the designer:
<sap:ActivityDesigner x:Class="ActivityValidation.DictionaryActivityDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
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"
Loaded="ActivityDesigner_Loaded">
<sap:ActivityDesigner.Resources>
<ResourceDictionary>
<sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<StackPanel Orientation="Vertical">
<Grid Name="ArgumentsGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition MinWidth="250" />
</Grid.ColumnDefinitions>
</Grid>
<sapv:ExpressionTextBox ExpressionType="s:String"
OwnerActivity="{Binding ModelItem}"
Expression="{Binding ModelItem.StringArg, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}" />
</StackPanel>
</sap:ActivityDesigner>
The InitializeGrid
method adds the expression text boxes for the arguments to the ArgumentGrid
. Under it I have a separate statically defined expression text box for a fixed argument in the activity to demonstrate the (almost) desired behavior.
Now for the problems:
Invalid expressions for the dynamic arguments only cause the error icon to appear beside the text box but it doesn't propagate to the top bar of the designer as it does if there is an error in the statically defined text box.
If I close the designer in such invalid state (and save the definition), the eror icon correctly propagates to the top bar even if the error is only in the dynamic text box. Though the behavior gets even more strange afterwards. After changing the values for the arguments, now even the error icon beside the text box doesn't work consistently any more.
If I delete the contents of a dynamic text box completely, the value in the dictionary gets set to null which manifests in the workflow definition as <x:Null x:Key="String Arg" />
instead of <InArgument x:TypeArguments="x:String" x:Key="String Arg">["a"]</InArgument>
or just ommiting the entry as is the case before editing the expression for the first time. If I reopen such a workflow even the statically created text box doesn't work properly any more (the error icon is only visible when text box is focused and it doesn't propagate to the top any more).
It seems obvious that I am doing something wrong when creating the dynamic text boxes. What would be the correct way of doing it? Is there any example available for creating a designer for a custom activity with dynamic number of arguments?
EDIT:
For those interested:
I encountered the problem I described here while trying to create a designer for a dynamic collection of arguments in an activity. I managed to work around the problem by using the built-in DynamicArgumentDialog window. I had to restructure my activity to accept a single collection of both input and output arguments:
public Dictionary<string, Argument> Arguments { get; set; }
instead of two separate collections I was using before:
public Dictionary<string, InArgument> InArguments { get; set; }
public Dictionary<string, OutArgument> OutArguments { get; set; }
I found the Custom Activity to Invoke XAML Based Child Workflows very helpful when making this work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With