1.1 Dependency Properties
细心的朋友可能在上一节为Content属性赋值的时候就发现了这么一段注释
这上面除了说明Content属性是System.Windows.Controls.ContentControl外,还提到这是个dependency property,那什么是dependency property,为何要使用这个东东。
1.1.1 Dependency Property 凭啥诞生
在WPF中大部分类都直接或间接的继承自DependencyObject这个抽象类,而为什么WPF要引进这个概念呢,难道只是为了增加新特性的噱头?其实一般WPF程序分为两部分,XAML和后台文件,XAML是从XML派生的界面标记语言,它与现存的.NET语言无关,而且XAML 是解析性的语言也就是说它和编译器无关是即时分析和编译的(事实上它也能被编译为BAML(Binary Application Markup Language)),而我们通常用的.NET语言C#,VB.NET,C++.NET等都是编译性语言.
又有人问了XAML是解释性语言,C# 是编译性语言那和Dependency又有何关呢,微软当年借着Longhorn推出的XAML时,如果有存在逻辑代码是需要把后台语言编译后才能看到XAML的呈现,当然时间的流逝带来的不仅仅是生理年龄的变化,还有技术的月新日益,我们现在使用VS2008便能使XMAL在后段代码改变后就能智能感知.
以上罗罗嗦嗦德说了一大堆,无非要说明的两个意思,一是XAMl 调用后端相当于调用一个dll,二是 虽然XAML对UI可读性方便了但无可避免地会对速度造成影响,那怎样才能提升效能呢,除了把XAML编译为BAML外,微软也在积极的寻找其他的优化点.后端自然是不能放过的,XAML是动态编译的当然它在找属性的时候就无非是反射了,然而反射虽然方便但对效率明显有影响.怎样才可以鱼和熊掌兼得?
要想快速定位一个属性还有一个方法就是static 了,运用static定义一个对应的散列表,还有一个好处,就是有数量级类的时候优势明显,举个例子,一个由CLR定义的类假定有三个属性(Property),而我们实例化这个类的时候无论是否需要用到这三个属性都需要为其分配一定的空间,当类的数量多了之后这样带来的性能损失也就越大,有了类似静态的散列表我们就可以在需要的时候才对其分配对应空间,而它的查找速度又几乎是1 ,如此何乐而不为.
其实Winform的Control内部也有类似的概念,当属性改变时它也有套机制来检测以便更新UI,或是验证信息.如今WPF把它直接提取出来,而且引用了static当然效率更高,我们的可操作性越广.
宗上所述其实Dependency的概念是为了弥补WPF效能上的缺陷,而前端XAML的Attribute规定映射的Property一定要Public 且 static 也是种人为的限定,大家此时应该也明白了为什么要有这两个硬性规定.
1.1.2 如何使用Dependency Property
首先要要明确的是依赖项属性只能由 DependencyObject 类型使用,具体实现的教程比较多了,贴个Adam Nathan在 《Windows Presentation Foundation Unleashed》中用的
{
// The dependency property
public static readonly DependencyProperty IsDefaultProperty;
static Button()
{
// Register the property
Button.IsDefaultProperty = DependencyProperty.Register(“IsDefault”, typeof(bool), typeof(Button),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsDefaultChanged)));
…
}
// A .NET property wrapper (optional)
public bool IsDefault
{
get { return (bool)GetValue(Button.IsDefaultProperty); }
set { SetValue(Button.IsDefaultProperty, value); }
}
// A property changed callback (optional)
private static void OnIsDefaultChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e) { … }
…
}
前一节中已经介绍了为什么 public static readonly DependencyProperty IsDefaultProperty;需要pulic 以及 static,至于为什么要用 readonly 那是为了不至于被外部调用的时候错误的覆盖掉,试想Button. IsDefaultProperty = null时,可会导致全部的Button的IsDefaultProperty都会无效,readonly不是硬性规定,如若确实需要在外部覆盖也可不加。
该示例是通过静态构造函数对IsDefaultProperty赋值的,方便起见也可以直接赋值。
DependencyProperty.Register(“IsDefault”,
IsDefaultProperty的名字当然也不是随便乱取的,注意后面一定要加Property才能被识别,前端XAML实际上看到是你Register时候的Name,Name+” Property” = PropertyName
typeof(bool), typeof(Button),
此种属性的类型和从属于哪个类,此示例中是bool类型从属于Button类
new FrameworkPropertyMetadata(false,
false是这个属性的默认值,如果是集合属性可以是new List<object>();引用类型直接为null
new PropertyChangedCallback(OnIsDefaultChanged)));
属性变动后所要进行的操作,[可选],其中OnIsDefaultChanged这个方法也是静态的
另外还有强制校正属性值(CoerceValueCallback)或者验证属性值(ValidateValueCallback)实际上他们的功能就是相当于普通的set方法中的一些验证.
public bool IsDefault
{
set
{
if (!ValidateValueCallback(value)) //对属性的验证
throw new Exception("Error Message");//如果验证不通过抛出异常
bool temp = CoerceValueCallback(value); //校正value的值
_isDefault = temp; //把变量赋值操作
PropertyChangedCallback(_isDefault); //属性赋值后的操作
}
}
CoerceValueCallback[可选]作用就是做个简单校正,比如
if (current < MinReading) current = MinReading;
if (current > MaxReading) current = MaxReading;
return current;
注意:不会强制属性的默认值。如果属性值仍然采用其初始默认值,或通过使用 ClearValue 清除其他值,则可能存在等于默认值的属性值。
当然在PropertyChangedCallback 中也可以强制回调,就是说会把值放到CoerceValueCallback委托中再跑一次,参数指定是DependencyProperty类型,如果值不是引用类型这次CoerceValueCallbackfa返回值不会改变当前值,一定要修改当前值的的话CoerceValueCallback委托的第一个参数可作修改
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
o.CoerceValue(SomeDependencyProperty);
}
虽然已经有IsDefaultProperty但是如果在XMAL中调用该属性却不声明的话编译时就会提示错误,为了安全起见VS的编译器应该是用反射校对一遍,在XAML调用的不是此属性而是静态字段(IsDefaultProperty),可如果我们没有定义IsDefaultProperty那么该IsDefault 也是可用的,系统会先找散列表,再通过反射来实现属性, 如果定义了DependencyProperty注意不要在get,set中添加其他的逻辑判断代码
{
get { return (bool)GetValue(Button.IsDefaultProperty); }
set { SetValue(Button.IsDefaultProperty, value); }
}
1.1.3 集合Dependency Property的使用
在上例中如果把bool类型改为List<bool> 类型,那么在调用的时候实例多个的话,然后分别在用add方法增加一些子集,你会惊奇的发现他们居然是公用一个List,那么如何解决这个方法呢, 应使用 SetValue(DependencyPropertyKey, Object) 方法通过只能在该类中访问的 DependencyPropertyKey 对该属性进行设置,这样就能独立运用了.如下所示:
public class Aquarium : DependencyObject
{
public Aquarium()
: base()
{
SetValue(AquariumContentsPropertyKey, new List<FrameworkElement>());
}
private static readonly DependencyPropertyKey AquariumContentsPropertyKey =
DependencyProperty.RegisterReadOnly(
"AquariumContents",
typeof(List<FrameworkElement>),
typeof(Aquarium),
new FrameworkPropertyMetadata(new List<FrameworkElement>())
);
public static readonly DependencyProperty AquariumContentsProperty =
AquariumContentsPropertyKey.DependencyProperty;
public List<FrameworkElement> AquariumContents
{
get { return (List<FrameworkElement>)GetValue(AquariumContentsProperty); }
}
}
从代码角度来说就是多了步DependencyPropertyKey的过程,而且注意到它是private static readonly
这一问题容易让人联想到引用类型的问题,不过用了引用类型不会出现这种公用地址的情况,不过使用引用类型不能把默认值直接实例化.可以赋值为null./default(class)
1.1.4 附加属性
这一概念咋听下感觉像扩展属性,想来扩展方法(Extend Method)和扩展属性都全了,这年月还真是幸福.不过这个需要另一个类的帮助,打个比方,我要一把伞,可我自己不能生成伞,只能由另外一个有伞的人给我一把.此概念常被用作父容器给子元素一个标示.具体的应用在DockPanel中使用较多:
CheckBox myCheckBox = new CheckBox();
myCheckBox.Content = "Hello";
myDockPanel.Children.Add(myCheckBox);
DockPanel.SetDock(myCheckBox, Dock.Top);
对于附加属性主要是这句DockPanel.SetDock(myCheckBox, Dock.Top); DockPanel有个可以附加的DependencyProperty属性DockProperty,它把这个属性通过SetDock这个方法赋给了myCheckBox,在SetDock方法中他进行了一系列的逻辑判断.
附加属性主要是通过RegisterAttached进行注册.再加两个和Name的Get和Set方法.
Get属性名 访问器的签名必须是:
public static object Get 属性名 (object target )
Set属性名 访问器的签名必须是:
public static void Set 属性名 (object target , object value )
例如:
"IsBubbleSource",
typeof(Boolean),
typeof(AquariumObject),
new FrameworkPropertyMetadata
(false,FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetIsBubbleSource(UIElement element, Boolean value)
{
element.SetValue(IsBubbleSourceProperty, value);
}
public static Boolean GetIsBubbleSource(UIElement element)
{
return (Boolean)element.GetValue(IsBubbleSourceProperty);
}
最后还是友情提示一句public static readonly DependencyProperty IsBubbleSourceProperty中的IsBubbleSourceProperty是 Name+” Property” 构成的.前面的标示一定要是public static readonly. 是因为要给XAML 使用,如果只是在后端为了给类一个临时存放的变量那么两个get,set方法也可以不实现,访问符也可以是private.
1.1.5 设置相应的元数据标志
上一段的示例中有这么一段 new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
FrameworkPropertyMetadataOptions 的作用是什么呢?
在属性更改后影响布局系统在页面中调整元素大小或呈现元素的方式,那么使UI呈现的最快就是我们要做的事.那我们是要判断它是哪种呈现,并设置它的标识(可以设置一至多个标识) 看MSDN给出的详细解释:
AffectsMeasure 指示在对此属性进行更改时,需要更改包含对象在父对象中可能需要更多或较少空间的 UI 呈现。例如,“Width”属性应设置此标志。
AffectsArrange 指示在对此属性进行更改时,需要更改通常无须更改专用空间、但又指示该空间中的位置已发生了更改的 UI 呈现。例如,“Alignment”属性应设置此标志。
AffectsRender 指示已发生了某些不会影响布局和度量,但确实需要其他呈现的其他更改。更改现有元素的颜色的属性便是一个示例,如“Background”。
这些标志在元数据中通常用作协议,以便您自己重写实现属性系统或布局回调。例如,如果实例的任何属性报告值发生更改,并且在其元数据中将 AffectsArrange 设置为 true,则您可能具有将调用 InvalidateArrange 的 OnPropertyChanged 回调。
1.1.6 小声议论
对于Dependency属性大家应该有个大概的了解,可能有人会说似乎定义声明使用这厮挺麻烦,对直接在后端CS文件中操作的哥们来说意义也不大,毕竟如果说是效率的话,那几个类所要费用的资源还是耗的起的,没用静态变量存储的那些年天也不还没塌下来,可前后端分工合作却是大趋势,以MS出3.0语法的一些角度来说,今后应该有优化的可能性.至于何时嘛,不好意思,我只是打酱油的。