代码改变世界

《WF编程》系列之38 - 依赖属性

2008-01-30 10:19  Windie Chai  阅读(5706)  评论(7编辑  收藏  举报
引进依赖属性的最终目标是管理状态.依赖属性并不是Windows Workflow专有的,与WF类似,同样使用XAML来表示的Windows Presentation Foundation也应用了依赖属性.在WF中,依赖属性拥有下列关键的功能:
  • 活动数据绑定
  • 附加属性
  • 元数据
每个使用依赖属性的类最终都继承自一个抽象类:DependencyObject.如下图所示,DependencyObject类提供了操作依赖属性的方法,比如GetValue和SetValue.

在为自定义组合活动创建UserName属性时我们已经使用到了一些DependencyObject类的方法.下面是设计器生成代码中的一小段,再来回顾一下:
public String UserName
{
get
{
return ((string)(base.GetValue(GetUploadActivity.GetUpload.UserNameProperty)));
}
set
{
base.SetValue(GetUploadActivity.GetUpload.UserNameProperty, value);
}
}

和我们通常定义的属性有所区别的是,UserName属性并没有依靠一个私有变量,而是使用GetValue方法和SetValue方法从父类获取和得到值.我们的活动之所以可以使用这些方法,是因为它最终继承自DependencyObject类.
我们不能直接操作依赖属性,只能通过GetValue和SetValue方法来操作它们.你可以将DependencyObject看作属性和代码的连接器.作为连接器的DependencyObject可以做一些神奇的事情,譬如数据绑定.
在DependencyObject的内部,有一个存储依赖属性及其值的Dictionary集合.还记得上边图片中的DependencyProperty类吗?这个类的实例将会扮演依赖属性集合中Key的角色.下面是设计器生成的另外一段代码:
public static DependencyProperty UserNameProperty = DependencyProperty.Register("UserName"//名称
typeof(System.String), //类型
typeof(GetUploadActivity.GetUpload));//拥有者的类型

当UserName调用base.GetValue或base.SetValue方法时,它将传递名为UserNameProperty的DependencyObject对象.它就是依赖属性集合的key.这个key提供了依赖属性的名称,类型及其拥有者的类型.
在依赖属性和普通属性之间作取舍之前,我们先来了解一下依赖属性能的三个功能:活动数据绑定,附加属性和元数据,看看它们如何让我们的开放更加简单.

5.4.1 活动数据绑定

有时我们不能在设计时设置属性的值,因为一些属性的值只有在运行时才可获取.举例来说,在设计时,我们已经知道CallExternalMethod活动工作所需的接口类型,所以我们可以设置它的InterfaceType属性.但同样在设计时,我们却不知道userName参数的值,因为直到用户运行工作流之前这个值都是不存在的.
所以我们把userName参数绑定到了UserName属性之上.在属性面板中可以看到CallExternalMethod活动的userName参数的属性升级表达式为:Activity=GetUploadActivity,Path=UserName.当我们为了获取userName参数的值而去调用GetValue方法时,依赖属性系统就会找到GetUploadActivity活动并返回其UserName属性的值.
活动数据绑定是一种用来连接运行时数据和活动属性的强大的机制,它和.NET的数据绑定(将数据源和UI元素绑定)类似.活动数据绑定的一个普遍用法是将一个活动的传出参数绑定到下一个活动的传入参数上,这样我们就不再需要手工在活动之间传递数据了.
实际上,绑定是由ActivityBind类实现的,这个类拥有Name和Path属性,并且允许SetValue和GetValue方法去查找活动和活动成员来执行绑定.下面是设计器为userName绑定时生成的代码:
ActivityBind activitybind1 = new ActivityBind();
activitybind1.Name 
= "GetUpload";
activitybind1.Path 
= "UserName";

我们可以将ActivityBind对象传递到DependencyObject的SetBinding方法中.SetBinding方法看起来和SetValue方法差不多,只是我们传入的不是属性的值,而是绑定信息.活动数据绑定也可以在XAML中表示.使用大括号的绑定语法如下所示:

理论上,我们可以使用C#或者Visual Basic中的普通属性来实现相同的绑定行为,但我们不得不为所有的属性编写get和set方法,依赖属性帮我们完成了这些费时的事情.

5.4.2 附加属性

我们可以为任何从DependencyObject继承而来的对象附加一个依赖属性.也就是说,我们可以在运行时使用自定义属性来扩展所有的Windows Workflow活动.
假如父活动需要为每个子活动追加信息,那么就会使用到附加属性.WF基本活动中就有一个现成的例子:ConditionedActivityGroup活动.在第四章我们介绍过CAG活动,它可以有条件的执行一个或多个子活动,因为它将When条件追加到了每一个子活动之上.在工作流设计器中,CAG的每个子活动都显示有自己的When属性,这正是依赖属性施展的魔法.它给CAG的每一个子活动都追加了When属性,正因如此,它们才可以在CAG内部工作.
让我们看看位于CAG内部的CodeActivity,我们编写一个名为MyCondition的CodeCondition方法.在设计器中,我们设置CodeActivity的When属性为MyCondition,接着设计器就会生成类似下面的代码:
CodeCondition codecondition1 = new CodeCondition();
codecondition1.Condition 
+= new System.EventHandler(MyCondition);
codeActivity1.SetValue(ConditionedActivityGroup.WhenConditionProperty, codecondition1);

通过调用SetValue将CAG的When属性添加到CodeActivity的属性集合中.当然,XAML也支持附加属性,下面是附加属性在XAML中的编定义方式:
<ConditionedActivityGroup x:Name="conditionedActivityGroup1">
  
<CodeActivity x:Name="codeActivity1" ExecuteCode="codeActivity1_ExecuteCode">
    
<ConditionedActivityGroup.WhenCondition>
      
<CodeCondition Condition="MyCondition" />
    
</ConditionedActivityGroup.WhenCondition>
  
</CodeActivity>
</ConditionedActivityGroup>

需要注意的是,如果活动需要将它的属性附加到其它活动之上时,它应该使用RegisterAttached方法来注册依赖属性(而不是Register方法).

5.4.3 元数据属性

依赖属性分为两种:元数据属性和实例属性.元数据属性的值必须在设计时指定,并且在运行时不可以更改.这意味着我们不可以绑定元数据属性,因为绑定就意味着会在运行时赋值;而实例属性则可以利用活动数据绑定来设置值,无论在设计时还是运行时.
元数据属性是确保活动完整性的安全网.举例来说,我们之前配置的CallExternalEvent活动,我们设置了InterfaceType和MethodName属性.基于以上两项设置,设计器列出了其它可供我们配置的属性(比如外部方法及其参数).就拿上述配置来说,如果在运行时改变InterfaceType和MethodName属性无疑是十分危险的,因为这样做会打乱其它所有的配置.正因如此,CallExternalEvent活动将这两个属性定义为元数据属性,以确保它们在运行时不可以被改变.
当我们调用Register方法将依赖属性注册到DependencyProperty集合时,我们可以选择是否传递PropertyMetaData对象. PropertyMetaData的构造函数可以接受一个DependencyPropertyOptions枚举参数来指定属性是否元数据属性.下面的代码注册了一个名为Interface的元数据属性:
public static DependencyProperty InterfaceTypeProperty = DependencyProperty.Register("InterfaceType",
typeof(Type),
typeof(GetUploadActivity),
new PropertyMetadata(DependencyPropertyOptions.Metadata));

5.4.4 依赖属性小结

现在来回答之前提出的问题:我们应该在什么时候使用普通属性,什么时候使用依赖属性呢?答案很简单:当我们想要定义一个元数据属性,附加属性,或者允许内部活动绑定到属性时,我们就需要依赖属性.这三个功能对于自定义活动的开发来说都具有重要的意义.介绍过依赖属性之后,让我们继续看看如何使用继承法创建自定义活动吧.