WF4.0 入门系列(八)Custom Activities and Designers 之一

我们知道,WF允许我们以代码方式创建自己的活动,我们可以根据自己的要求来继承不同的活动基类。

基类 用途
Activity 由其他活动组成的活动
CodeActivity 可以控制工作流执行的活动
AsyncCodeActivity 可以在工作流执行过程中进行异步操作的活动
NativeActivity 由其他活动组成的活动,并且可以访问工作流运行时

 

1、在我们现有的解决方案中添加一个活动设计器库HelloWorkflow.Activities

WF4.0 入门系列(八)Custom Activities and Designers 之一_第1张图片

2、删除项目中原有的ActivityDesigner1.xaml,添加活动WriteTwoLines.xaml.


3、向设计器中添加一个Sequence活动,为此活动创建两个输入参数(string类型)Line1和Line2,然后在Sequence活动中添加两个WriteLine基元,分别输出Line1和Line2的值。


我们刚刚创建了第一个自定义活动WriteTwoLines,这个活动的创建方式是:组合了WF提供的活动。

4、现在我们开始创建更为复杂的自定义活动,其中一个活动DiagnosticTraceActivity用于跟踪程序运行,另一个活动是PrePostSequenceActivity(顾名思义,是在用户提供的活动运行前后加上Pre(预处理)和Post(尾处理)操作)。而且我们希望我们的新活动在设计器窗口中能够显示成下面的样子

WF4.0 入门系列(八)Custom Activities and Designers 之一_第2张图片


要达到这样的效果,我们必须编写四个类,类DiagnosticTrace.cs和PrePostSequence.cs用于继承Activity类,以实现业务逻辑;另外类DiagnosticTraceDesigner.xaml和PrePostSequenceDesigner.xaml则用wpf的语法编写,用以界面显示;

4.1 创建DiagnosticTrace.cs

该类继承于CodeActivity,同时从图中可以看到该活动有三个成员,Category和Level是设计时可赋值的成员,而Text是在设计时和运行时都可以赋值的成员。所有的成员都以属性的形式存在于活动中。

    [System.ComponentModel.DefaultValue(null)]
    public InArgument<string> Text { get; set; }

    /// <summary> 跟踪类型 </summary> 
    [System.Windows.Markup.DependsOn("Text")]
    [DefaultValue(null)]
    public string Category { get; set; }

    /// <summary> 当前信息的跟踪级别 </summary>
    [DependsOn("Category")]
    [DefaultValue("Off")]
    public System.Diagnostics.TraceLevel Level { get; set; }


其中DependsOnAttribute指示特性化属性依赖于另一个属性的值,如Category依赖于Text属性的值,该设置在序列化XAML或在WPF中使用Setter时会用到。除了定制一些属性之外,自定义活动最重要的就是重写基类的Execute方法,表示活动的执行过程。该方法只有一个参数CodeActivityContext context 是执行活动时所处的执行上下文。

    /// <summary>在派生类中实现时,执行该活动</summary> 
    protected override void Execute(CodeActivityContext context)
    {
        //context执行活动时所处的执行上下文。GetValue获取输入参数Text的值
        string text = context.GetValue(this.Text);

        switch (Level)
        {
            //输出错误处理消息
            case System.Diagnostics.TraceLevel.Error:
                System.Diagnostics.Trace.TraceError(text);
                break;
            //输出信息性消息、警告和错误处理消息
            case System.Diagnostics.TraceLevel.Info:
                Trace.TraceInformation(text);
                break;
            //输出所有调试和跟踪消息
            case System.Diagnostics.TraceLevel.Verbose:
                Trace.WriteLine(text, Category);
                break;
            case System.Diagnostics.TraceLevel.Warning:
                //使用指定的消息向 System.Diagnostics.Trace.Listeners 集合中的跟踪侦听器中写入警告消息
                Trace.TraceWarning(text);
                break;
        }

        //not(不输出跟踪和调试消息)
        if (Level != System.Diagnostics.TraceLevel.Off)
        {
            //CustomTrackingRecord包含由运行时跟踪基础结构在引发自定义跟踪记录时发送到跟踪参与者的数据。
            CustomTrackingRecord trackRecord = new System.Activities.Tracking.CustomTrackingRecord(Category, Level);
            trackRecord.Data.Add("Text", text);
            trackRecord.Data.Add("Category", Category);

            //将指定的自定义跟踪记录发送到已注册的全部跟踪提供程序
            context.Track(trackRecord);
        }
    }
缓存元数据,将输入/输出参数,变量,验证信息等加入到基类的元数据集合中
    protected override void CacheMetadata(CodeActivityMetadata metadata)
    {
        var textArg = new RuntimeArgument("Text", typeof(string), ArgumentDirection.In, true);
        metadata.AddArgument(textArg);

        if (string.IsNullOrEmpty(Category))
            metadata.AddValidationError("Category must not be empty");
    }
完整代码如下:
public sealed class DiagnosticTrace : CodeActivity
{
    const string DefaultRecordName = "Diagnostic Trace";

    public DiagnosticTrace()
    {
        Level = System.Diagnostics.TraceLevel.Off;
        Category = DefaultRecordName;
    }

    [System.ComponentModel.DefaultValue(null)]
    public InArgument<string> Text { get; set; }

    [System.Windows.Markup.DependsOn("Text")]
    [DefaultValue(null)]
    public string Category { get; set; }

    [DependsOn("Category")]
    [DefaultValue("Off")]
    public System.Diagnostics.TraceLevel Level { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        string text = context.GetValue(this.Text);

        switch (Level)
        {
            case System.Diagnostics.TraceLevel.Error:
                System.Diagnostics.Trace.TraceError(text);
                break;
            case System.Diagnostics.TraceLevel.Info:
                Trace.TraceInformation(text);
                break;
            case System.Diagnostics.TraceLevel.Verbose:
                Trace.WriteLine(text, Category);
                break;
            case System.Diagnostics.TraceLevel.Warning:
                Trace.TraceWarning(text);
                break;
        }

        if (Level != System.Diagnostics.TraceLevel.Off)
        {
            CustomTrackingRecord trackRecord = new System.Activities.Tracking.CustomTrackingRecord(Category, Level);
            trackRecord.Data.Add("Text", text);
            trackRecord.Data.Add("Category", Category);

            context.Track(trackRecord);
        }
    }

    protected override void CacheMetadata(CodeActivityMetadata metadata)
    {
        var textArg = new RuntimeArgument("Text", typeof(string), ArgumentDirection.In, true);
        metadata.AddArgument(textArg);

        if (string.IsNullOrEmpty(Category))
            metadata.AddValidationError("Category must not be empty");
    }
}

 


5、PrePostSequence.cs

此类的目的功能是使用户可以创建自己的子活动集合(类似Sequence),同时还提供预处理和尾处理功能。PrePostSequence继承于NativeActivity类

私有成员

    Collection<Activity> activities;
    Collection<Variable> variables;

    //定义一个表示预处理是否完成的bool类型变量,变量名称为PreCompleted(此名称显示在设计器窗口中)
    Variable<bool> preCompleted = new Variable<bool>() { Name = "PreCompleted" };
    Variable<bool> bodyCompleted = new Variable<bool>() { Name = "BodyCompleted" };
    //上一次执行的body中的子活动的序号
    Variable<int> lastIndexHint = new Variable<int>() { Name = "LastIndexHint" };

    //CompletionCallback 活动完成后调用的方法
    CompletionCallback onChildCompleted;
    CompletionCallback onPreCompleted;
公开属性
    /// <summary> 活动包含的变量集合 </summary>
    public Collection<Variable> Variables
    {
        get
        {
            if (variables == null)
            {
                variables = new Collection<Variable>();
            }
            return variables;
        }
    }

    /// <summary> 预处理活动 </summary>
    /// <remarks> 使用DependsOn特性来控制属性在XAML文件中的出场顺序 </remarks>
    [DependsOn("Variables")]
    public Activity Pre { get; set; }

    [DependsOn("Pre")]
    public Collection<Activity> Activities
    {
        get
        {
            if (activities == null)
            {
                activities = new Collection<Activity>();
            }
            return activities;
        }
    }

    [DependsOn("Activities")]
    public Activity Post { get; set; }

    /// <remarks>
    /// 使用这种方式创建和缓存回调函数可以提高性能,因为回调函数可以一次创建,在子活动中多次使用
    /// </remarks>    
    CompletionCallback OnChildCompletedCallback    
    {        
        get        
        {            
            if (onChildCompleted == null)            
            {                
                onChildCompleted = new CompletionCallback(OnChildCompleted);            
            }            
            return onChildCompleted;        
        }    
    }    

    /// <summary>    
    /// The callback for when the Pre activity is completed    
    /// </summary>    
    CompletionCallback OnPreCompletedCallback    
    {        
        get        
        {            
            if (onPreCompleted == null)            
            {                
               onPreCompleted = new CompletionCallback(OnPreCompleted);            
            }            
            return onPreCompleted;        
        }    
    }

辅助方法

/// <summary> 尾处理活动不为空 </summary>
    private bool PostIsNotEmpty()
    {
        return Post != null;
    }

    /// <summary> 子活动是否没有结束 </summary> 
    private bool ActivitiesHaveNotCompleted(NativeActivityContext context)
    {
        return bodyCompleted.Get(context) == false;
    }

    /// <summary> 子活动不为空 </summary> 
    private bool ActivitiesCollectionIsNotEmpty()
    {
        return Activities.Count > 0;
    }

    /// <summary> 预处理活动是否没有结束 </summary> 
    private bool PreHasNotCompleted(NativeActivityContext context)
    {
        return preCompleted.Get(context) == false;
    }

    /// <summary> 预处理活动是否存在 </summary> 
    private bool PreActivityExists()
    {
        return Pre != null;
    }

 

缓存元数据

 

    /// <remarks> 当基类运行时会使用反射来查找我们创建的变量和子活动。通过重写此方法可以避免反射从而提高效率 </remarks>
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        /// 将私有变量添加到实现变量的元数据集合中。
        metadata.AddImplementationVariable(lastIndexHint);
        metadata.AddImplementationVariable(preCompleted);
        metadata.AddImplementationVariable(bodyCompleted);

        //将指定的变量集合添加到活动的变量中。
        metadata.SetVariablesCollection(Variables);

        metadata.AddChild(Pre);
        metadata.AddChild(Post);

        // 将指定的活动添加到子活动的元数据列表中。
        foreach (Activity activity in Activities)
        {
            metadata.AddChild(activity);
        }
    }

 

执行活动

 

    protected override void Execute(NativeActivityContext context)
    {            
        ScheduleChildActivities(context);
    }

    /// <summary>计划执行子活动集合Schedules the child activities</summary>
    private void ScheduleChildActivities(NativeActivityContext context)
    {
        if (PreActivityExists() && PreHasNotCompleted(context))
        {
            //计划指定的 NativeActivity 子活动Pre的执行,使用指定的完成回调位置,活动完成后在该位置恢复父进程。
            //参数1:该子活动是由在父 NativeActivity 的执行过程中发生的事件触发。
            //参数2:一个书签,用于指示 ActivityAction 完成后恢复父活动执行的位置
            context.ScheduleActivity(Pre, OnPreCompletedCallback);
        }
        else if (ActivitiesCollectionIsNotEmpty() && ActivitiesHaveNotCompleted(context))
        {
            context.ScheduleActivity(Activities.First(), OnChildCompletedCallback);
        }
        else if (PostIsNotEmpty())
        {
            context.ScheduleActivity(Post);
        }
    }

    /// <summary> 当预处理活动完成时执行 </summary>
    private void OnPreCompleted(NativeActivityContext context, ActivityInstance completedInstance)
    {
        //设置预处理完成标志为true
        preCompleted.Set(context, true);
        ScheduleChildActivities(context);
    }

    /// <summary> 当子活动中的一个完成时被调用 </summary> 
    private void OnChildCompleted(NativeActivityContext context, ActivityInstance completedInstance)
    {
        // 获取已完成的子活动的下标
        int completedInstanceIndex = lastIndexHint.Get(context);
        int nextChildIndex = completedInstanceIndex + 1;

        // 如果存在没有执行的子活动,继续执行下一个子活动
        if (nextChildIndex < Activities.Count)
        {
            lastIndexHint.Set(context, nextChildIndex);
            Activity nextChild = Activities[nextChildIndex];
            context.ScheduleActivity(nextChild, OnChildCompletedCallback);
        }
        else // Completed body
        {
            bodyCompleted.Set(context, true);
            ScheduleChildActivities(context);
        }
    }
指定活动的Content属性为Activities属性
    [ContentProperty("Activities")]
    public sealed class PrePostSequence : NativeActivity
    {

6、在HelloWorkflow.Activities项目中添加一个文件夹Designers,添加两个活动设计器文件DiagnosticTraceDesigner.xaml,PrePostSequenceDesigner.xaml。其中DiagnosticTraceDesigner.xaml内容如下:

WF4.0 入门系列(八)Custom Activities and Designers 之一_第3张图片

<ObjectDataProvider x:Key="TraceLevelValues" MethodName="GetValues" ObjectType="{x:Type s:Enum}" >
                <ObjectDataProvider.MethodParameters>
                    <x:Type TypeName="sd:TraceLevel" />
                </ObjectDataProvider.MethodParameters>
            </ObjectDataProvider>

<!--收缩时仅仅显示表达式文本框。-->
<DataTemplate x:Key="Collapsed">
    <StackPanel Orientation="Horizontal">
        <TextBlock VerticalAlignment="Center" Margin="5">
            Text
        </TextBlock>
        <!--表达式文本框。ModelItem就是此设计文件对应的活动DiagnosticTrace。ModelItem.Text就是这个类实例的Text属性,双向绑定-->
        <sapv:ExpressionTextBox 
            HintText="Text Expression"
            Expression="{Binding Path=ModelItem.Text, Mode=TwoWay, 
            Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In }"
            ExpressionType="s:String"
            OwnerActivity="{Binding Path=ModelItem}"
            Width="300" Margin="0,5" MaxLines="1" />
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="Expanded">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <TextBlock VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" Margin="5">
                Text
            </TextBlock>
            <sapv:ExpressionTextBox 
                HintText="Text Expression"
                Expression="{Binding Path=ModelItem.Text, Mode=TwoWay, 
                Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In }"
                ExpressionType="s:String" 
                OwnerActivity="{Binding Path=ModelItem}"
                Width="300" Margin="0,5" Grid.Row="0" Grid.Column="1" MaxLines="1" />

            <TextBlock VerticalAlignment="Center" Grid.Row="1" Grid.Column="0" Margin="5" >            
                Category
            </TextBlock>

            <TextBox Text="{Binding Path=ModelItem.Category, Mode=TwoWay, 
                Converter={StaticResource ModelToObjectValueConverter}}"
                Width="300" Margin="0,5" Grid.Row="1" Grid.Column="1" MaxLines="1">
            </TextBox>

            <TextBlock Name="txtCenter" VerticalAlignment="Center" Grid.Row="2" Grid.Column="0" Margin="5" >
                Trace Level
            </TextBlock>
            <ComboBox ItemsSource="{Binding Source={StaticResource TraceLevelValues}}"
                SelectedValue="{Binding Path=ModelItem.Level, Mode=TwoWay, Converter={StaticResource ModelToObjectValueConverter}}"
                VerticalAlignment="Center" Grid.Row="2" Grid.Column="1" Margin="0,5">
            </ComboBox>
        </Grid>
    </DataTemplate>

    <Style x:Key="ExpandOrCollapsedStyle" TargetType="{x:Type ContentPresenter}">
        <Setter Property="ContentTemplate" Value="{DynamicResource Expanded}"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=ShowExpanded}" Value="false">
                <Setter Property="ContentTemplate" Value="{DynamicResource Collapsed}"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

PrePostSequenceDesigner.xaml总体结构和上面相同,都包含两个DataTemplate,只不过相对来说其名为Expanded的DataTemplate更为复杂一点,包括三个方框Border,同时还设置了多个自活动之间的连接符(一个红色的三角箭头)

WF4.0 入门系列(八)Custom Activities and Designers 之一_第4张图片

    <DataTemplate x:Key="Collapsed">
        <StackPanel>
            <TextBlock FontStyle="Oblique" Foreground="Gray">请双击查看</TextBlock>
        </StackPanel>
    </DataTemplate>

收缩时仅显示一个文本

<DataTemplate x:Key="Expanded">
    <StackPanel>
        <Border BorderBrush="Green" BorderThickness="4" CornerRadius="2" Margin="3">
            <StackPanel>
                <TextBlock HorizontalAlignment="Center" FontWeight="Bold">Pre-Activity</TextBlock>
                <sap:WorkflowItemPresenter Margin="3" Item="{Binding Path=ModelItem.Pre, Mode=TwoWay}" HintText="请拖拽活动到这里"/>
            </StackPanel>
        </Border>
        <Border BorderBrush="Red" BorderThickness="4" CornerRadius="2" Margin="3">
            <StackPanel>
                <TextBlock HorizontalAlignment="Center" FontWeight="Bold">Activities</TextBlock>
                <sap:WorkflowItemsPresenter Margin="3" Items="{Binding Path=ModelItem.Activities}" HintText="Drop activities here">
                    <sap:WorkflowItemsPresenter.SpacerTemplate>
                        <DataTemplate>
                            <Grid>
                                <Path Fill="Red" Width="20" Height="30" HorizontalAlignment="Center" Data="M 0,10 l 20,0 l -10,10 Z" />
                            </Grid>
                        </DataTemplate>
                    </sap:WorkflowItemsPresenter.SpacerTemplate>
                    <sap:WorkflowItemsPresenter.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Vertical"/>
                        </ItemsPanelTemplate>
                    </sap:WorkflowItemsPresenter.ItemsPanel>
                </sap:WorkflowItemsPresenter>
           </StackPanel>
        </Border>
        <Border BorderBrush="Black" BorderThickness="4" CornerRadius="2" Margin="3">
            <StackPanel>
                <TextBlock HorizontalAlignment="Center" FontWeight="Bold">Post-Activity</TextBlock>
                <sap:WorkflowItemPresenter Margin="3" Item="{Binding Path=ModelItem.Post, Mode=TwoWay}" HintText="Drop activity here"/>
            </StackPanel>
        </Border>
    </StackPanel>
</DataTemplate>

 

SpaceTemplate配置节定义了活动之间的连接符的形状为一个向下的红色三角形,在Data属性中描述。从原点(0,0)M移动到(0,10);l划线,向x轴方向划20,y轴0,即从point(0,10)到point(20,10)之间划了一条线;同理连线(20,10)-->(10,20);最后z连回起始点(0,10)

ItemTemplate节定义活动显示方式,多个活动间垂直摆放。

由于Pre和Post都是单个活动,所以使用WorkflowItemPresenter设计器,而Activities是活动集合,所以使用WorkflowItemsPresenter设计器
WorkflowItemPresenter

7、关键步骤,将cs文件和xaml关联起来,即为活动指定相应的设计器。否则即使你为活动创建了设计器,该设计器也无法使用。
[ContentProperty("Activities")] //指示类型的哪个属性是 XAML 内容属性
    [Designer(typeof(PrePostSequenceDesigner))]
    public sealed class PrePostSequence : NativeActivity
    {

[System.ComponentModel.Designer(typeof(DiagnosticTraceDesigner))]
    public sealed class DiagnosticTrace : CodeActivity
    {

 

HelloWorkflow项目中添加对HelloWorkflow.Activities和WPF类库PresentationFramework的引用,重新生成解决方案后,可以看到在工具栏中增加了三个活动

WF4.0 入门系列(八)Custom Activities and Designers 之一_第5张图片

你可能感兴趣的:(入门,WF4.0)