De base, utiliser la généricité dans une activité n’est pas compliquée. Pour coder une activité et utiliser un argument générique, le code suivant suffit :

public class Increment<T> : Activity
{
    // Define an activity input argument of type T
    public InOutArgument<T> To { get; set; }

    // ...
}

La réécriture de la méthode CacheMetaData n’est pas nom plus des plus ardus :

/// <summary>
/// Register activity's metadata
/// </summary>
/// <param name="metadata"></param>
protected override void CacheMetadata(ActivityMetadata metadata)
{
    // Register In arguments
    RuntimeArgument arg = new RuntimeArgument("To", typeof(T), ArgumentDirection.InOut);
    metadata.AddArgument(arg);
    metadata.Bind(this.To, arg);

    // [To] Argument must be set
    if (this.To == null)
    {
        metadata.AddValidationError(
            new System.Activities.Validation.ValidationError(
                "[To] argument must be set!",
                false,
                "To"));
    }
}

Les choses se corsent à partir du moment où l’on veut associer un designer à notre activité. Pour avoir un designer tel que celui-ci (ma capture représente les deux états de l’activité : à ):

 Workflow1

Deux points peuvent être bloquants :

  • Si l’argument est de type sortant (Out ou InOut) car la MSDN n’est pas très fournie en exemples de ce genre.
  • Le convertisseur à utiliser avec le control ExpressionTextBox car le type est inconnu.

On peut très bien coder à la hussarde et ne pas se soucier du convertisseur, mais on s’expose à d’éventuelles surprises lors de conversions.

Pour répondre à ces deux problématiques j’ai coder un petit exemple simple d’ActiviyDesigner :

<sap:ActivityDesigner x:Class="Demo.Wf.GenericActivity.IncrementDesigner"
    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:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
    xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
    >
    <sap:ActivityDesigner.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/Generic.xaml" />
            </ResourceDictionary.MergedDictionaries> 
            <sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter"/>
        </ResourceDictionary>
    </sap:ActivityDesigner.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        
        <sapv:ExpressionTextBox x:Name="To" Grid.Column="0"
            OwnerActivity="{Binding Path=ModelItem}"
            Expression="{Binding Path=ModelItem.To, Mode=TwoWay,
            Converter={StaticResource ResourceKey=ArgumentToExpressionConverter},
            ConverterParameter=InOut}" 
            HintText="Argument [To] à incrémenter"
            UseLocationExpression="True" />
        <TextBlock x:Name="Label" Grid.Column="1" Text="++" />
    </Grid>
</sap:ActivityDesigner>

Comme on peut le voir la particularité de mon Xaml réside dans le fait de définir la propriété UseLocationExpression à vrai sur l’ExpressionTextbox associé à mon argument générique To. La propriété ExpressionType n’est pas définie ici, mais via le code C# du designer après chargement du designer (avant le ModelItem est vide).

namespace Demo.Wf.GenericActivity
{
    // Logique d'interaction pour IncrementDesigner.xaml
    public partial class IncrementDesigner
    {
        public IncrementDesigner()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(IncrementDesigner_Loaded);
        }

        private void IncrementDesigner_Loaded(object sender, RoutedEventArgs e)
        {
            Type t = this.ModelItem.Properties["To"].PropertyType.GetGenericArguments()[0] ;
            
            this.To.ExpressionType = t;
        }
    }
}

Rien de bien compliqué en soit, mais la manipulation du proxy ModelItem donnant des migraines à plus d’un développeur, j’ai préféré présenter le code complet ici ;)

Au prochain article je vous présente le code pour réaliser sa propre activité i++ dans les bonnes pratique de WF4 ;)

 

PS: ne cherchez pas après mon dictionnaire Xaml, il me sert juste à avoir un mise en forme un peu plus sympathique.