《WF编程》系列之39 – 自定义活动:继承法与活动组件

从根本上来说,采用组成法或继承法来创建自定义活动并没有太大的差别.之前我们创建的GetUploadActivity就是从SequenceActivity类继承的.组成法和继承法都使用了继承.
继承法相对组成法来说更容易理解.在组成法中,我们关注于如何组织自定义活动内的子活动,并且创建了活动属性和执行模型.而继承法是一种相对更加初级的方法.让我们用继承法编写一个向控制台输出字符串的自定义活动吧.

5.5.1 CONSOLEWRITEACTIVITY

创建这个活动的第一步,我们编写一个简单的类.不需要XAML,也不需要设计器,仅仅使用下面的C#代码:

using System;
using System.Workflow.ComponentModel;
using System.ComponentModel;
using System.Workflow.ComponentModel.Design;
public class ConsoleWriteActivity : Activity
{
public string Text
{
get { return _text; }
set { _text = value; }
}
private string _text;

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
Console.WriteLine(Text);
return ActivityExecutionStatus.Closed;
}
}

这个类从System.Workflow.ComponentModel.Activity类继承而来.我们知道,Activity是WF中所有活动的基类.我们的活动有一个简单的属性Text,而且并没有被升级为依赖属性,也就是说它不可以进行数据绑定.但我们仍然可以在工作流设计器中设置这个属性的值为字符串.

这个类最重要的功能是Execute方法.通过重写Execute方法,我们就可以完全控制对活动的行为.当活动运行时,Runtime会调用我们编写的Execute方法,然后将Text属性输出到控制台.然后我们返回一个结果为Closed的执行状态以通知Runtime活动完成了执行过程.

这时,我们可以编译自定义活动,接下来创建一个新的工作流.打开工作流设计器,就可以在工具箱中(当然,我们需要引用包含这个活动的程序集)看到这个活动.如果我们将其拖拽到设计器中,就会发现这个活动看起来和基本类库的其它活动差不多.

注意活动的Text属性也显示在了属性面板中(Misc分类下).我们可以通过System.ComponentModel和System.Workflow.ComponentModel命名空间中的特性来控制这个属性的设计时功能.举例来说,添加下面的代码到Text属性可以提供一个默认值,描述和属性的分类.

[DefaultValue("")]
[Description("The text to write to the console")]
[Category("Activity")]
public string Text
{
get { return _text; }
set { _text = value; }
}

特性并不是只能附加在属性上,我们还可以用特性来控制活动的行为和外观.

5.5.2 活动组件

我们可以关联活动组件(activity component)到我们的自定义活动,籍此来调整它的行为.两个重要的组件类型是活动设计器和活动验证器.我们使用特性来将组件和活动进行关联.而且很明显可以知道这些组件是可选的,因为前边的自定义活动范例就不需要使用任何组件.但是,许多优秀的自定义活动都或通过使用这些组件来增强设计器体验.

在前面创建的自定义活动中,如果工作流设计者永远都不给Text属性分配值得话会发生什么状况呢?我们的ConsoleWrite活动在缺少有效的Text属性的情况下是不可用的,所以我们必须事先就假定编写者会犯这个错误.如果我们为自定义活动关联了验证器,那我们就可以给编写者一个恰当的反馈.

5.5.2.1 活动验证器

活动验证器在工作流设计和编译之间执行,它的作用是确保活动的配置在运行时都正确.想要执行验证,我们需要编写一个新的验证器类,让它继承ActivityValidator类,并重写父类的Validate方法.下面的Validate方法将确保我们的自定义控件中没有空的Text属性:

public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
{
ValidationErrorCollection errors = base.Validate(manager, obj);
ConsoleWriteActivity activity = obj as ConsoleWriteActivity;
if (activity.Parent != null && String.IsNullOrEmpty(activity.Text))
{
errors.Add(ValidationError.GetNotSetValidationError("Text"));
}
return errors;
}

我将所有的验证错误都添加到一个ValidationErrorCollection集合并且返回.需要注意的是,在这段代码中我们还检查了这个活动是否有父活动.这是因为Validate方法在我们编译自定义活动自身时也会执行,那么如果这时活动包含一个空的Text属性,编译时就会得到一个”属性Text的值没有设置”的错误.而如果活动有父活动存在,就可以认为活动在工作流内部,这时它才是需要验证的.

接着,我们通过使用ActivityValidator特性将验证器附加到自定义活动上.

[ActivityValidator(typeof(ConsoleWriteValidator))]
public class ConsoleWriteActivity : Activity
{
//
}

活动验证器并不对属性检查做任何限制,也就是说,我们可以检查任何事情.例如,我们可以决定自定义活动是否只能作为While活动的子活动.我们可以检查父活动的类型,如果发现类型不是While活动的话就抛出验证错误.

5.5.2.2 活动设计器

活动设计器用来控制活动在设计时的外观和行为.我们需要编写一个新的设计器类,使它从ActivityDesigner类继承并重载父类的虚拟方法.通过重载OnPaint方法,我们可以在设计器中画出自己的活动形状.我们还可以像Windows UI开发人员那样重载诸如OnMouseDown,OnDragOver等许多方法.下列代码为我们的自定义活动实现了设计时外观:

public class ConsoleWriteDesigner : ActivityDesigner
{
ConsoleWriteActivity _activity;
protected override void Initialize(Activity activity)
{
_activity = activity as ConsoleWriteActivity;
base.Initialize(activity);
}

protected override Size OnLayoutSize(ActivityDesignerLayoutEventArgs e)
{
return new Size(120, 70);
}

protected override void OnPaint(ActivityDesignerPaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Black,Location.X, Location.Y,Size.Width, Size.Height);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
Rectangle rect = new Rectangle(Location.X, Location.Y,Size.Width, 15);
e.Graphics.DrawString(Activity.QualifiedName,DesignerTheme.Font,Brushes.Yellow, rect, format);
using (Font font = new Font("Lucida Console", 7))
{
e.Graphics.DrawString("> " + _activity.Text, font,Brushes.White, rect.X, rect.Y + 20);
}}}

我们可以在Initialize方法中获取自定义活动自身的引用;在OnLayoutSize方法中告诉设计器活动的尺寸;在OnPaint方法中绘制活动的外观.上面的代码中,我们模仿命令行窗口的样子画了一个黑色的背景并将活动的Text属性用白色的字体输出.编译后的活动在设计器中的效果如下:

当然,为了使设计器生效,我们需要为自定义活动关联设计器.

[ActivityValidator(typeof(ConsoleWriteValidator))]
[Designer(typeof(ConsoleWriteDesigner))]
public class ConsoleWriteActivity : Activity
{
//
}

本节的自定义活动非常简单.如果我们想要创建高级的自定义活动,我们还需要深入了解ActivityExecutionContext类的相关知识(如果感到眼熟就对了,我们在自定义活动的Execute方法中见过这个类)和活动生命周期的相关内容.

4 Comments

  1. lMl

    那一个Activity的Designer实例是怎么和Activity本身的实例对应起来的呢?
    请赐教~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注