4 WPF依赖属性

理解依赖属性

依赖属性支持的特征包括:动画、数据绑定、样式。由WPF元素暴露的属性大部分都是依赖属性。依赖属性和常规属性的使用方法相同。

WPF设计了依赖属性支持其特有的动态特性,并且不干扰其他系统的.net代码。

定义依赖属性

创造一个依赖属性的语法完全不同于创造一个普通的.NET属性。

第一步是定义一个代表属性的对象。这是DependencyProperty类的一个实例。关于你属性的信息需要一直是可用的,甚至可能在类之间共享(常见在WPF元素中)。因此,你的DependencyProperty对象必须被定义为相关类的一个静态字段。

例如,FrameworkElement类定义了一个Margin属性。Margin被所有元素共享,是一个依赖属性:

public class FrameworkElement: UIElement, ...

{

    public static readonly DependencyProperty MarginProperty;

    ...

}

根据命名约定,定义依赖属性的字段名字是正常属性加上单词Property后缀。那样,你能区分依赖属性定义和实际属性的名字。字段使用readonly关键字,这意味着它只能在FrameworkElement类的静态构造函数中设置,如何设置见下节。

注册依赖属性

下一步是注册你的依赖属性。因为要在使用属性之前完成注册,必须在相关类的一个静态构造函数中执行它。

不能直接实例化DependencyProperty对象,因为DependencyProperty类没有公开的构造函数。代替,一个DependencyObject实例只能使用静态的DependencyProperty.Register()方法被创造。DependencyProperty对象被创造之后不能再修改,因为所有的DependencyProperty成员是只读的。代替,它们的值必须作为Register()方法的参数被提供。

下面例子显示FrameworkElement类的Margin属性是如何被注册的:

static FrameworkElement()

{

    var metadata = new FrameworkPropertyMetadata(

      new Thickness(), FrameworkPropertyMetadataOptions.AffectsMeasure);



    MarginProperty = DependencyProperty.Register("Margin",

      typeof(Thickness), typeof(FrameworkElement), metadata,

      new ValidateValueCallback(FrameworkElement.IsMarginValid));

    ...

}

注册一个依赖属性包括二步。首先,你创造一个FrameworkPropertyMetadata对象,指明希望使用依赖属性的什么服务(诸如支持数据绑定,动画,和日志)。其次,依靠调用DependencyProperty.Register()静态方法注册属性。这时,你负责提供一些关键成分:

  • 属性名称
  • 属性的数据类型
  • 属性所在类的类型
  • 可选,附带有属性设置的FrameworkPropertyMetadata对象
  • 可选,执行属性验证的回调

两个可选属性值得研究。FrameworkPropertyMetadata的详细描述见95页。

包装依赖属性

创造依赖属性的最后一步是用一个传统.NET属性包装它。WPF属性使用的是定义在DependencyObject基类的GetValue()和SetValue()方法。

public Thickness Margin

{

    set { SetValue(MarginProperty, value); }

    get { return (Thickness)GetValue(MarginProperty); }

}

当你创造属性包装时,你应该仅调用SetValue()和GetValue(),如在前例中。你不应该添加任何额外的代码验证值,引起事件,等等。那是因为另外的特征可能旁路属性包装,直接调用SetValue()和GetValue()。(一个例子是在运行时当一个编译XAML文件被解析时。)SetValue()和GetValue()都是公开的。

验证输入值应使用DependencyProperty.ValidateValueCallback。

引发事件应使用FrameworkPropertyMetadata.PropertyChangedCallback 。

现在可以使用属性了:

myElement.Margin = new Thickness(5);

之后,可能希望移除局部值设置,仿佛你从未设置它。依靠从DependencyObject继承的ClearValue()方法。

myElement.ClearValue(FrameworkElement.MarginProperty);

使用依赖属性

依赖属性支持两个关键的行为:改变通知和动态值求解。

改变通知

如果你希望对一个属性的改变作出反应,你有二个选择—创造一个绑定,或写一个触发器。但是,依赖属性没有提供响应属性值改变的事件。

动态值求解

依赖属性因动态值求解的行为而得名。一个依赖属性依赖于多个属性提供者,每个提供者带有它自己的优先级。当你从一个属性值取回一个值时,WPF属性系统经历一系列步骤求得最终值。首先,它通过考虑下列因素决定属性的基值。优先级从最低的到最高排列(最下面的赢):

  1. 默认值(由FrameworkPropertyMetadata对象设置)
  2. 继承的值(如果FrameworkPropertyMetadata.Inherits标记被设置,并且一个值已经应用到某个祖先元素)
  3. 主题样式值
  4. 工程样式值
  5. 本地值(使用代码或标记直接设置的值)

基值不一定是最终的属性值,WPF通过4个步骤求得属性值:

  1. 基值
  2. 表达式(数据绑定和资源)
  3. 动画
  4. 通过CoerceValueCallback修正值

共享依赖属性

一些类共享依赖属性,即使他们有独立的类层次结构。例如,TextBlock.FontFamily和Control.FontFamily都指向同一个依赖属性,它实际上被定义在TextElement类中以及TextElement.FontFamilyProperty。TextElement类的静态构造函数注册属性,但是TextBlock和Control类的静态构造函数简单地调用DependencyProperty.AddOwner()方法重用它:

TextBlock.FontFamilyProperty =

  TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));

自定义类

副作用

附加依赖属性

附加属性是依赖属性,并且它被WPF属性系统管理。区别是应用附加属性的类不是定义它的类。

为定义一个附加属性,你使用RegisterAttached()方法而不是Register()。这是一个注册Grid.Row属性的例子:

var metadata = new FrameworkPropertyMetadata(

  0, new PropertyChangedCallback(Grid.OnCellAttachedPropertyChanged));



Grid.RowProperty = DependencyProperty.RegisterAttached("Row", typeof(int),

  typeof(Grid), metadata, new ValidateValueCallback(Grid.IsIntValueNotNegative));

就像一个普通的依赖属性,你能提供一个FrameworkPropertyMetadata对象和一个ValidateValueCallback。

当创造附加属性时,你没有定义.NET属性包装。那是因为附加属性能被设置在任何依赖对象上。例如,Grid.Row属性可能被设置在一个Grid对象上(如果你有一内部嵌套另一个Grid的Grid)或在另外的一些元素上。事实上,即使元素不在一个Grid内,和即使在你的元素树上不存在Grid对象,Grid.Row属性也能被设置在那个元素上。

代替使用一个.NET属性包装,附加属性要求能调用的一对静态方法去设置和获得属性值。这些方法使用熟悉的SetValue()和GetValue()方法(从DependencyObject类继承)。静态的方法应该被命名SetPropertyName()和GetPropertyName()。

这里是实现Grid.Row附加属性的静态方法:

public static int GetRow(UIElement element)

{

    if (element == null)

    {

        throw new ArgumentNullException("");

    }

    return (int)element.GetValue(Grid.RowProperty);

}

public static void SetRow(UIElement element, int value)

{

    if (element == null)

    {

        throw new ArgumentNullException("");

    }

    element.SetValue(Grid.RowProperty, value);

}

使用代码设置一个元素位于网格的第一行:

Grid.SetRow(txtElement, 0);

可选地,你能直接调用SetValue()或GetValue()方法,旁路掉静态方法:

txtElement.SetValue(Grid.RowProperty, 0);

属性验证

WPF提供两种方法阻止无效值:

  • ValidateValueCallback:这回调能接受或拒绝新值。通常,这回调被用来捕获违反属性的约束明显错误。提供它作为DependencyProperty.Register()方法的参数。
  • CoerceValueCallback:这回调能修改新值为更可接受的值。通常,这回调被用来处理被设置在同一对象的依赖属性值之间的冲突。这些值可能单独是有效的,但是当应用在一起时不一致。为使用这回调,当创造FrameworkPropertyMetadata对象时,提供它作为构造函数的参数,此对象随后被传递到DependencyProperty.Register()方法。

这里是当应用程序试图设置一个依赖属性时:所有的部件如何起作用:

  1. 首先,CoerceValueCallback方法有机会修改提供值(通常,为使它与其它属性一致)或返回DependencyProperty.UnsetValue,这完全拒绝改变。
  2. 其次,ValidateValueCallback被引发。这方法返回真接受一个值作为有效,或返回假拒绝它。不同于CoerceValueCallback,ValidateValueCallback不访问设置属性的实际对象,这意味着你不能检查其它的属性值。
  3. 最后,如果先前两个阶段都成功了,PropertyChangedCallback被触发。这时,你能引发事件去通知其它类。

Validation回调

相当于正常属性的set部分中的验证。

其签名为接受一个object输入参数,返回布尔值。返回值为真表示接受,为假表示拒绝。

private static bool IsMarginValid(object value)

{

    Thickness thickness1 = (Thickness) value;

    return thickness1.IsValid(true, false, true, false);

}

有一个限制,它是一个静态方法,不能访问正被验证的对象,不能使用对象中的其他属性。

Coercion回调

用于验证互相关联的属性

private static object CoerceMaximum(DependencyObject d, object value)

{

    RangeBase base1 = (RangeBase)d;

    if (((double) value) < base1.Minimum)

    {

        return base1.Minimum;

    }

    return value;

}

你可能感兴趣的:(WPF)