Windows Workflow Foundation:创建自定义复合活动(二)

翻译|其它|编辑:郝浩|2006-05-12 12:27:00.000|阅读 1791 次

概述:

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>

行为

既然已完成了活动的可视表示,接下来就是对行为进行处理。在此只有一个类 Validator

Validator 类

该类主要用于在设计时向用户显示提示,说明活动处于不一致的状态,且通常用于在尚未定义必需属性的情况下显示警告。

在验证程序中,您可能想检查某个特定属性是否为非空,或者验证是否已向活动添加了适当数量的子活动。下面是我在我的代码中使用的示例,在此示例中我检查 ParallelIfActivity 是否至少包含两个分支:

using System;
using System.Drawing.Drawing2D;
using System.Workflow.ComponentModel.Design;

namespace MNS.Activities
{
[ToolboxItem(typeof(ParallelIfToolboxItem))]
[ToolboxBitmap(typeof(ParallelIf),"Resources.ParallelIf.png")]
[ActivityValidator(typeof(ParallelIfValidator))]
public class ParallelIfActivity :CompositeActivity, ...
  {
  }

public class ParallelIfValidator :CompositeActivityValidator
  {
public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
    {
       if (null == manager) 
        throw new ArgumentNullException("manager"); 
        if (null == obj) 
        throw new ArgumentNullException("obj"); 

        ParallelIfActivity pif = obj as ParallelIfActivity; 

        if (null == pif) 
                throw new ArgumentException("此验证程序只能由 ParallelIfActivity 使用", "obj"); 

                ValidationErrorCollection errors = base.Validate(manager, obj); 

                if ( null != pif.Parent )
                      {
                        // 现在实际验证该活动...
                        if ( pif.Activities.Count < 2 ) 
                        {
                                ValidationError err = new ValidationError ( "ParallelIf 至少需要两个分支", 100, false );
                                err.UserData.Add("LESS_THAN_TWO", "分支数少于两个");
                                errors.Add(err);
                        }
                }
       return errors;
    }
  }
}

在我的验证程序代码中,我先检查输入参数,然后调用返回现有验证错误(如果有)集合的基类 Validate 方法。然后检查 ParallelIfActivity 的子节点的数量,如果少于两个,则构造一个 ValidationError 实例并将其添加到返回的错误集合。如果从此方法返回错误,则工作流设计器将在活动旁边显示一个图标,并使用来自 ValidationError 对象的错误字符串,作为向用户显示的内容。

在此示例中,将向验证错误 UserData 集合添加“LESS_THAN_TWO”标记,该标记由设计器拾取并用于为活动创建适当数量的子分支。

创建验证程序时,我总是会建议调用基类 Validate() 方法,因为除了您自己确定的错误以外,基类中可能还有其他应该报告给用户的错误。

请注意,有两个主要的 Validator 类:ActivityValidatorCompositeActivityValidator。前者用于所有简单活动,后者用于所有包含子活动的活动。主要区别是用于 CompositeActivityValidator 的基类 Validate() 方法也将验证所有子活动,因此您可能会收到有关父活动的一长串错误列表,这些错误与子活动的所有错误相关。

在执行验证程序的情况下进行编译

当您将验证程序与活动关联后,特别是当您已从 Windows Workflow Beta 1 版升级后,您可能会发现,由于目前在编译期间会执行验证程序,因此无法实际编译代码。这是 Beta 2 版中的新情况,正因如此,我想要介绍如何避免此问题。

例如以下代码,这是来自我最初为 WriteLineActivity(下载代码中附带)编写的验证程序中的代码。

public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
{
    if (null == manager)
      throw new ArgumentNullException("manager");
        if (null == obj)
            throw new ArgumentNullException("obj");

            // 获取错误的基本集
        ValidationErrorCollection err = base.Validate(manager, obj);

        WriteLine wl = obj as WriteLine;

       if (null == wl)
            throw new ArgumentException("对象不是 WriteLine 活动", "obj");

       if ( null == wl.Message )
             err.Add(ValidationError.GetNotSetValidationError("Message"));

       return err;
}

这段代码检查是否已为 WriteLine 类的 Message 属性输入了内容,并在尚未输入任何文本时显示验证错误,如图 4 中所示。

图 4:在执行验证程序的情况下进行编译时出现的错误

至少可以说这个错误让人感到沮丧 - 我要设法的就是编译代码而已(甚至未使用 WriteLineActivity),因此很烦恼。这是 Beta 2 版中的新情况,我认为这是在 Windows Workflow 的最终发布版本之前需要更改的地方。要避免此问题,可以采取两种方法。

第一种方法是检查验证程序中是否有要验证的活动的父活动。如果有父活动,则某个工作流中在使用您的自定义活动,因此应该进行验证。但是,如果没有父活动,则您的活动在仅进行编译时不需要进行验证。因此,为了避免这个编译时的问题,只需向验证程序添加一行代码,用于检查是否存在父活动,如果有父活动,才进行验证。

if (null != wl.Parent)
{
   if (string.IsNullOrEmpty(wl.Message))
   errors.Add(ValidationError.GetNotSetValidationError("Message"));
}

这项检查与一般情况相悖,无疑会使许多开发人员感到意外,这正是我在本文中介绍它的原因。

另一种避免这个编译错误的方法是在常规“类库”项目中创建工作流活动,而不是在“工作流活动库”项目中创建。在这种情况下,编译代码时根本不会运行验证程序,因此您可以不必使用以上代码对父活动进行检查。就我个人而言,喜欢用此方法来避免在编译活动时运行验证程序。

您可能想知道,在代码中为什么将检查定义为 if (null != wl.Parent) 而不是 if ( wl.Parent != null )。它只是一个顽固的老习惯而已 - 我过去曾是 C++ 程序员,这种顺序的表达式较为安全,因为在使用 C++ 时,可能无意中遗漏了“!”字符,这会将空值指定给 wl.Parent。因此,我对赋值采用的所有方式可能看起来偏离正常方式,但它们均源自我多年前养成的好习惯。

自定义活动

ParallelIfActivity

既然已定义了设计器、验证程序和主题,那么实现 ParallelIfActivity 的代码就相当简单了。

[ToolboxBitmap(typeof(ParallelIfActivity), "Resources.ParallelIfActivity.png")]
[ToolboxItem(typeof(ParallelIfToolboxItem))]
[Designer(typeof(ParallelIfDesigner))]
[ActivityValidator(typeof(ParallelIfValidator))]
public partial class ParallelIfActivity :CompositeActivity, 
IActivityEventListener < ActivityExecutionStatusChangedEventArgs >
{
private static readonly DependencyProperty IsExecutingProperty = 
DependencyProperty.Register("IsExecuting",
typeof(bool),
typeof(ParallelIf));

public ParallelIf()
   {
InitializeComponent();
   }

/// <summary>
/// 用于指示活动是否在执行的标志
/// </summary>
public bool IsExecuting
    {
get { return (bool)base.GetValue(ParallelIf.IsExecutingProperty); }
set { base.SetValue(ParallelIf.IsExecutingProperty, value); }
    }
}

这段代码定义了指向所有已定义的 UI 类和 Behavior 类的链接,并定义了在执行程序中使用的并保存表明当前是否在执行活动的值的布尔标志。

执行活动

运行工作流时,会对活动调用 Execute 方法,其目的是调用每个子活动并执行每个子活动。执行的顺序完全取决于您,但最可能的是您将遍历所有启用的子活动(如果这是一个复合活动),然后按顺序执行每个子活动。

在 ParallelIfActivity 示例中,必须根据与活动的分支关联的条件确定是否执行该分支。如果该条件的评估结果为 true,则执行该分支的活动。

下面的代码显示了如何对每个分支评估条件以验证是否应该执行该分支。

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
if (null == executionContext)
throw new ArgumentNullException("executionContext");

// 设置标志以指示我正在执行
this.IsExecuting = true;
bool hasStarted = false;

for (int child = 0; child < this.EnabledActivities.Count; child++)
    {
ParallelIfBranch branch = this.EnabledActivities[child] as ParallelIfBranch;

if (null != branch)
        {
           // 评估条件并仅执行返回 true 的分支
           if (branch.Condition.Evaluate(branch, executionContext))
            {
               // 现在注册一个回调以在子项关闭时获得通知
               branch.RegisterForStatusChange(Activity.ClosedEvent, this);

               // 然后执行子活动
               executionContext.ExecuteActivity(branch);

               // 用于确定执行方法的结果
               hasStarted = true;
            }
        }
    }

return hasStarted ?ActivityExecutionStatus.Executing :ActivityExecutionStatus.Closed ;
}

在此示例中对输入参数进行验证,然后遍历所有可执行的活动 - 这些活动均定义为尚未在设计器中注释掉的活动。之后对这些活动中的每个活动来评估条件,如果为 true,则将活动加入队列,以使用 executionContext.ExecuteActivity() 方法调用来执行。另外,还设置了一个标志,表明至少有一个活动在执行,该标志用在方法结束后以返回适当的状态代码。如果有子活动在执行,则返回 ActivityExecutionStatus.Executing,否则返回 ActivityExecutionStatus.Closed

因此工作流运行时需要通过某种方式知道并行活动已完成 - 这通过添加子活动完成时出现的事件处理程序来实现,即添加对 RegisterForStatusChange 的调用,如代码中所示。然后,可以验证所有子活动是否都已完成,如果是,则调用 ActivityExecutionContext.CloseActivity(),如下面的代码中所示。

void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(
object sender, ActivityExecutionStatusChangedEventArgs e)
{
if (null == sender)
throw new ArgumentNullException("sender");
if (null == e)
throw new ArgumentException("e");

ActivityExecutionContext context = sender as ActivityExecutionContext;

if (null == context)
throw new ArgumentException("发送方必须为 
ActivityExecutionContext", "sender");

// 检查进行调用时的状态...
if (e.ExecutionStatus == ActivityExecutionStatus.Closed)
    {
// 我们已为此活动中的已关闭事件完成侦听
e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);

// 现在检查是否所有子活动均已关闭...
ParallelIfActivity pif = context.Activity as ParallelIfActivity;

bool finished = true;

for (int branch = 0; branch < pif.EnabledActivities.Count; branch++)
        {
Activity child = pif.EnabledActivities[branch];

if ((child.ExecutionStatus != ActivityExecutionStatus.Initialized) && (child.ExecutionStatus != 
ActivityExecutionStatus.Closed))
finished = false;
        }

// 所有子项均已完成 - 就此结束
if (finished)
context.CloseActivity();
    }
}

这段代码只是遍历了每个子活动,并检查是否存在既非“已初始化”又非“已关闭”的活动 - 在这种情况下,仍有子活动在执行,因此无法关闭父活动。

上面介绍的状态更改代码可能因使用了泛型而看起来有些奇怪,但是从概念上讲,它非常简单。每个活动均有一组在其生存期间出现的事件,如 ExecutingEventCancelingEventClosedEvent 等。这些事件都定义为 Activity 类的相关属性。

在您调用 activity.RegisterForStatusChange () 方法时,将委派传递给在活动状态更改时调用的特定函数。您的委派可能需要处理多个状态更改,如果这样,可以通过检查传递的事件参数为“e”的 ExecutionStatus 属性来查找活动的当前状态。

在内部,对于每个可能出现的事件,都保持着一个委派列表,且在状态更改时会遍历此列表并调用每个委派。

除了上面介绍的 Execute 方法和 OnEvent 方法以外,我还提供了 Cancel 方法和 OnActivityChangeAdd 方法的实现。在 Cancel 方法中,需要取消所有执行的分支,其中涉及遍历所有执行的子活动,并对这些子活动调用 CancelActivity 方法。

例如,如果您的代码向 ParallelIfActivity 动态添加节点,则可能会在运行时调用 OnActivityChangeAdd 方法。在这种情况下,我需要为新添加的活动注册状态更改处理程序,然后执行该活动。这些方法的代码可通过下载获得。

ParallelIfBranch 活动

这是 ParallelfActivity 的子活动,包含由执行程序评估以确定是否应该执行分支的 Condition 属性。此活动的完整代码如下所示。

[ToolboxItem(false)]
[Designer(typeof(ParallelIfBranchDesigner))]
[ActivityValidator(typeof(ParallelIfBranchValidator))]
public class ParallelIfBranch :SequenceActivity
{
/// <summary>
/// 将条件属性定义为 DependencyProperty
/// </summary>
public static readonly DependencyProperty ConditionProperty = 
DependencyProperty.Register("Condition",
typeof(Condition),
typeof(ParallelIfBranch),
new PropertyMetadata(DependencyPropertyOptions.Metadata));

/// <summary>
/// 获取/设置条件
/// </summary>
/// <remarks>
/// 运行时评估条件,如果为 true 则将执行子活动
/// </remarks>
public Condition Condition
    {
get { return base.GetValue(ConditionProperty) as Condition; }
set { base.SetValue(ConditionProperty, value); }
    }
}

除了此活动以外,还有一个验证程序和一个设计器:验证程序用来确保已为 Condition 属性定义了值;设计器只是替换 CanBeParentedTo 方法来确保可以将 ParallelIfBranch 仅作为 ParallelIfActivity 的直接子活动来添加。

现用活动

由于本文的其中一点是向可用活动库提供新活动,因此本部分将介绍如何能够在工作流中使用 ParallelIfActivity。在下载代码中,已包含了可以用于将此活动添加到工具箱的完整实现。这样,您可以将 ParallelIfActivity 拖到设计器中,并将得到与下面的图 5 中所示类似的结果。

图 5:工作流中的 ParallelIfActivity

默认情况下,会定义 ToolboxItem 类以向 ParallelIfActivity 添加三个分支。由于尚未定义活动的 Condition 属性,因此这些会导致显示错误。

按顺序选择每个分支并定义条件 - 在此您可以使用代码条件或规则条件。为每个分支定义了条件后,应该能够执行工作流。我已在下载代码中包含了可以用于将数据输出到控制台的 WriteLineActivity,这在检查已实际执行了 ParallelIf 的哪些分支时很有用。

另一种将数据输出到控制台的方法是,在活动执行时使用 Windows Workflow 的内置跟踪服务来记录额外数据。为了执行此操作,可以调用 Activity 类的 TrackData () 方法来在跟踪存储中记录任何想要的自定义数据。


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com


为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP