Windows Workflow Foundation:创建自定义复合活动
适用于:
Windows Workflow Foundation Beta 2 版
Microsoft Visual C# 2.0 版
Visual Studio 2005
摘要:本 文构思了一个自定义复合活动,并表明如何在工作流中使用该活动。
注意:本文中的内容是使用 Beta 2 版编写的,将来可能需要进行一些更改才能适用于 Windows Workflow Foundation 的更高版本。
下载代码示例: Windows Workflow 示例 - ParallelIf.msi。
本页内容
简介
可视类
行为
自定义活动
现用活动
详细信息
关于作者
简介
在本文中,我将深入探讨针对 Windows Workflow Foundation (WF) 开发自定义活动的详细信息,
为了创建活动,您需要掌握许多类 - 在此示例中,将创建一个称为 ParallelIf 的活动,该活动的运行方式类似于常规的并行活动;
下面图 1 中所示图像表明了将在本文中使用的类。
我特意将这些类分为左右两侧,
可视类
有三个主要类用于构成活动的可视方面:ToolboxItem、
ToolboxItem 类
我将介绍的第一个类是 ToolboxItem 类。
该类的主要作用是定义将您的活动从工具箱拖到工作流设计器中时会
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Runtime.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
namespace MNS.Activities
{
public class ParallelIfToolboxItem :ActivityToolboxItem
{
protected override IComponent[] CreateComponentsCore(IDesignerHost host)
{
CompositeActivity activity = new ParallelIfActivity();
activity.Activities.Add(new ParallelIfBranch());
activity.Activities.Add(new ParallelIfBranch());
activity.Activities.Add(new ParallelIfBranch());
return new IComponent[] { activity };
}
// 为清晰表述而忽略了其它方法
}
[ToolboxItem(typeof(ParallelIfToolboxItem))]
[ToolboxBitmap(typeof(ParallelIfActivity),
"Resources.ParallelIfActivity.png")]
public partial class ParallelIfActivity :CompositeActivity,
IActivityEventListener < ActivityExecutionStatusChangedEventArgs >
{
...
}
}
我已在此处定义了 ParallelIfToolboxItem 类并添加了 CreateComponentsCore 方法的实现。该方法用于构造新的父活动 (ParallelIfActivity),然后添加三个 ParallelIfBranch 活动作为新活动的子活动(在本文后面将定义这些活动)。
然后显示了 ParallelIfActivity 类的第一个部分,
我在此处创建了一个图像,并将其定义为嵌入的资源,
Designer 类
为了在窗体上绘制活动时更改活动的可视方面,
在设计器中,您可以添加代码以提供完整的自定义绘制功能(
下面的代码表明了设计器类的实现,以及如何通过 [Designer] 属性将其与 ParallelIfActivity 类关联。
using System;
using System.Collections.ObjectModel;
using System.Workflow.ComponentModel.Design;
namespace MNS.Activities
{
[Designer(typeof(ParallelIfDesigner))]
public partial class ParallelIfActivity :CompositeActivity, ...
{
...
}
public class ParallelIfDesigner :ParallelActivityDesigner
{
public override bool CanInsertActivities(HitTestInfo
insertLocation, ReadOnlyCollection<Activity> activitiesToInsert)
{
return false;
}
public override bool CanMoveActivities(HitTestInfo moveLocation,
ReadOnlyCollection<Activity> activitiesToMove)
{
return true ;
}
public override bool CanRemoveActivities(ReadOnlyCollection<Activity>
activitiesToRemove)
{
return true ;
}
protected override CompositeActivity OnCreateNewBranch()
{
return new ParallelIfBranch();
}
}
}
设计器类用于确定在工作流设计器中会发生什么,因此我在此替换了 CanInsertActivities 以返回 false。此方法会在某活动从工具箱拖到 ParallelIfActivity 中时调用,并且我始终在此返回 false,因为只有 ParallelIfBranch 可以作为 ParallelIfActivity 的直接子活动(由于是使用 OnCreateNewBranch() 方法添加 ParallelIfBranch 的,因此它不在工具箱中)。您可以扩展此处理过程,
我从 CanRemoveActivities 和 CanMoveActivities 返回 true,因为在我的设计器中支持这两者。对 ParallelActivityDesigner 的 CanRemoveActivities 方法的默认处理是检查定义了多少子分支,以及允许删除某分支(
复合活动(例如此活动)需要一些创建新分支的方法,在此分支是 ParallelIfActivity 的直接子活动。当您右键单击活动,然后选择 Add Branch(添加分支)时,即在设计器中使用 OnCreateNewBranch 方法,如图 3 中所示。
此外,您可以在从 OnCreateNewBranch 方法返回之前,再次设置分支的相应属性(如果需要)。
从验证程序(下文予以介绍)
private void OnAddBranch(object sender, EventArgs e)
{
CompositeActivity activity1 = this.OnCreateNewBranch();
CompositeActivity activity2 = base.Activity as CompositeActivity;
if ((activity2 != null) && (activity1 != null))
{
int num1 = this.ContainedDesigners.Count;
Activity[] activityArray1 = new Activity[] { activity1 };
CompositeActivityDesigner.InsertActivities(this, new
ConnectorHitTestInfo(this, HitTestLocations.Designer,
activity2.Activities.Count), new
List<Activity>(activityArray1).AsReadOnly(), string.Format("正在添加分支 {0}", activity1.GetType().Name));
if ((this.ContainedDesigners.Count > num1) &&
(this.ContainedDesigners.Count > 0))
{
this.ContainedDesigners[this.ContainedDesigners.Count - 1].EnsureVisible();
}
}
}
protected override void OnExecuteDesignerAction(DesignerAction designerAction)
{
// 检查我的用户数据是否存在...
if (designerAction.UserData.Contains("LESS_THAN_TWO"))
{
CompositeActivity parent = this.Activity as CompositeActivity;
if (null != parent)
{
while ( parent.Activities.Count < 2 )
OnAddBranch(this, EventArgs.Empty);
}
}
else
base.OnExecuteDesignerAction(designerAction);
}
因此,如果用户从 ParallelIfActivity 删除所有活动,则在用户界面上将显示警告,提示如果单击,
Theme 类
Theme 类用于定义活动的可视表示,在此您可以定义属性,例如,
Theme 类有许多其他属性,因此您可以了解一下还可以定义其他什么属性。
using System;
using System.Drawing.Drawing2D;
using System.Workflow.ComponentModel.Design;
namespace MNS.Activities
{
[ActivityDesignerTheme( typeof (ParallelIfTheme))]
public class ParallelIfDesigner :ParallelActivityDesigner
{
...
}
public class ParallelIfTheme :CompositeDesignerTheme
{
public ParallelIfTheme(WorkflowTheme theme)
: base(theme)
{
this.ShowDropShadow = true;
this.ConnectorStartCap = LineAnchor.None;
this.ConnectorEndCap = LineAnchor.None;
this.BorderStyle = DashStyle.Dash;
}
}
}
主题的类型主要有两种 - 用于常规活动的主题和用于复合活动的主题。
主题通过使用 [ActivityDesignerTheme] 属性附加到设计器,如上面的代码中所示。而设计器通过使用 [Designer] 属性附加到活动,如前面的代码段中所示。
行为
既然已完成了活动的可视表示,接下来就是对行为进行处理。
Validator 类
该类主要用于在设计时向用户显示提示,
在验证程序中,您可能想检查某个特定属性是否为非空,
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;
}
}
}
在我的验证程序代码中,我先检查输入参数,
在此示例中,将向验证错误 UserData 集合添加“LESS_THAN_TWO”标记,
创建验证程序时,我总是会建议调用基类 Validate() 方法,因为除了您自己确定的错误以外,
请注意,有两个主要的 Validator 类:ActivityValidator 和 CompositeActivityValidator。前者用
在执行验证程序的情况下进行编译
当您将验证程序与活动关联后,特别是当您已从 Windows Workflow Beta 1 版升级后,您可能会发现,由于目前在编译期间会执行验证程序,
例如以下代码,这是来自我最初为 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 中所示。
至 少可以说这个错误让人感到沮丧 - 我要设法做的就是编译代码而已(甚至未使用 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 示例中,必须根据与活动的分支关联的条件确定是否执行该分支。
下面的代码显示了如何对每个分支评估条件以验证是否应该执行该分
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 ;
}
在此示例中对输入参数进行验证,然后遍历所有可执行的活动 - 这些活动均定义为尚未在设计器中注释掉的活动。
因此工作流运行时需要通过某种方式知道并行活动已完成 - 这通过添加子活动完成时出现的事件处理程序来实现,即添加对 RegisterForStatusChange 的调用,如代码中所示。然后,可以验证所有子活动是否都已完成,
void IActivityEventListener<ActivityExecutionStatusChanged EventArgs>.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();
}
}
这段代码只是遍历了每个子活动,并检查是否存在既非“已初始化”
上面介绍的状态更改代码可能因使用了泛型而看起来有些奇怪,
在您调用 activity.
在内部,对于每个可能出现的事件,都保持着一个委派列表,
除了上面介绍的 Execute 方法和 OnEvent 方法以外,我还提供了 Cancel 方法和 OnActivityChangeAdd 方法的实现。在 Cancel 方法中,需要取消所有执行的分支,
例如,如果您的代码向 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); }
}
}
除了此活动以外,还有一个验证程序和一个设计器:
现用活动
由 于本文的其中一点是向可用活动库提供新活动,
默认情况下,会定义 ToolboxItem 类以向 ParallelIfActivity 添加三个分支。由于尚未定义活动的 Condition 属性,因此这些会导致显示错误。
按 顺序选择每个分支并定义条件 - 在此您可以使用代码条件或规则条件。为每个分支定义了条件后,
另一种将数据输出到控制台的方法是,在活动执行时使用 Windows Workflow 的内置跟踪服务来记录额外数据。为了执行此操作,可以调用 Activity 类的 TrackData () 方法来在跟踪存储中记录任何想要的自定义数据。
详细信息
有许多可以用来获取有关 Windows Workflow Foundation 的详细信息的站点。有关详细信息,请访问 http://msdn.microsoft.com/
另外,还有一本有关 Windows Workflow 的很有帮助的介绍性书籍,该书由 Microsoft 小组的几个主要成员编写而成,可在线获取。有关详细信息,请参阅 Presenting Windows Workflow Foundation(英文)。
关于作者
Morgan 在英国的 Microsoft 工作,他是 C#、Windows Forms 和 ASP.NET. 领域专业的应用程序开发顾问。自从 .NET Framework 诞生之日起,他就一直从事这方面的工作。请访问开发技术支持服务 (PSfD) 小组博客站点 http://blogs.msdn.com/psfd 和 Morgan 的站点 http://www.morganskinner.com。