在WPF中使用了更高级的依赖项属性替换了.net中的属性。依赖属性具有一些更高效的保存机制,同进支持附加功能,如 更改通知(Change Notification)以及属性值继承(在元素树中向下传递默认属性值)。依赖属性同样还是WPF中Animation,Binding,Style的重要基础。.
注意:只能为依赖对象(继承自DependencyObject)添加依赖属性。WPF中基础结构的关键部分中大部分都间接继承自DependencyObject类。
例子:
WPF中最常见的属性之一就是Margin属性。它在FramewordElement类中被定义,所有元素都共享自该属性。下面是FrameworkElement类的源函数
public class FrameworkElement:UIElement
{
[CommonDependencyProperty]
public static readonly DependencyProperty MarginProperty;
}
注意DependencyProperty类没有公有的构造函数。故只能使用DependencyProperty.Register()方法来创建DependencyProperty的实例。DependencyProperty.Register()同样是一个静态的方法。下面是该方法的定义:
//
// 摘要:
// 使用指定的属性名称、属性类型、所有者类型、属性元数据和属性的值验证回调来注册依赖项属性。
//
// 参数:
// name:
// 要注册的依赖项对象的名称。
//
// propertyType:
// 属性的类型。
//
// ownerType:
// 正注册依赖项对象的所有者类型。
//
// typeMetadata:
// 依赖项对象的属性元数据。(可选)
//
// validateValueCallback:
// 对回调的引用,除了典型的类型验证之外,该引用还应执行依赖项对象值的任何自定义验证。(可选)
//
// 返回结果:
// 一个依赖项对象标识符,应使用它在您的类中设置 public static readonly 字段的值。
//然后,在以后使用该标识符引用依赖项对象,用于某些操作,例如以编程方式设置其值,或者获取元数据。
public static DependencyProperty Register(
string name,
Type propertyType,
Type ownerType,
PropertyMetadata typeMetadata,
ValidateValueCallback validateValueCallback);
注册一个依赖属性主要有两个步骤
1.创建一个FrameworkPropertyMetadata对象,该对象指示了希望通过依赖属性来使用什么服务(如支持动画,数据绑定等)。
FrameworkPropertyMetadata的主要属性如下:
public bool AffectsArrange { get; set; }
public bool AffectsMeasure { get; set; }
public bool AffectsParentArrange { get; set; }
public bool AffectsParentMeasure { get; set; }
public bool AffectsRender { get; set; }
public bool BindsTwoWayByDefault { get; set; }
public UpdateSourceTrigger DefaultUpdateSourceTrigger { get; set; }
public bool Inherits { get; set; }
public bool IsDataBindingAllowed { get; }
public bool IsNotDataBindable { get; set; }
public bool Journal { get; set; }
public bool OverridesInheritanceBehavior { get; set; }
public bool SubPropertiesDoNotAffectRender { get; set; }
2.通过调用DependencyProperty.Register()静态方法来注册属性。
static FrameworkElement()
{
FrameworkPropertyMetadata metadata=new FrameworkPropertyMetadata(
new Thickness(),FrameworkPropertyMetadataOptions.AffectsMeasure);
MarginProperty =DependencyProperty.Register
("Margin",
typeof(Thickness),
typeof(FrameworkElement),
metadata,
new ValidateValueCallback(FrameworkElement.IsMarginValid));
}
创建依赖属性的最后一步是使用传统的.net属性包装WPF依赖属性。注意WPF属性使用GetValue和SetValue。下面是示例:
FrameworkElement:UIElement
{
public Thickness Margin
{
set{ SetValue(MarginProperty, value); }
get{return(Thickness)GetValue(MarginProperty);}
}
}
注意:当创建属性包装器时,不应当包含任何额外的验证属性值的代码,引发事件的代码,等等。因为WPF的其他功能可能会忽略属性包装器的代码而直接调用SetValue和GetValue方法(比如在Xaml中直接为属性赋值)。事件的触发应当写在FrameworkPropertyMetadata.PropertyChangedCallback的回调函数中。
在上一节中提到如果想在属性修改时能够自动引发事件,就应当实现FrameworkPropertyMetadata.PropertyChangedCallback。
这是因为在WPF中,当属性值发生变化时,依赖项属性不会自动引发事件,它们会触发一个受保护的名为OnPropertyChangedCallback()的方法。此方法通过WPF的Binding和触发器传递信息,并调用PropertyChangedCallback回调函数。
//
// 摘要:
// 用指定的默认值和回调初始化 System.Windows.PropertyMetadata 类的新实例。
//
// 参数:
// defaultValue:
// 依赖项对象的默认值,通常作为某种特定类型的值提供。
//
// propertyChangedCallback:
// 对处理程序实现的引用,每当属性的有效值更改时,属性系统都将调用该处理程序实现。
//
// coerceValueCallback:
// 对处理程序实现的引用,每当属性系统对该属性调用 System.Windows.DependencyObject.CoerceValue(System.Windows.DependencyProperty)
// 时都将调用此处理程序实现。
//
// 异常:
// System.ArgumentException:
// defaultValue 不能设置为值 System.Windows.DependencyProperty.UnsetValue;
public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback);
只要实现了PropertyChangedCallback 就能够实现属性值更改的自动响应。
当属性值非法时,对于传统的.net属性,可以在属性设置器中捕获这类问题。在WPF中,依赖属性使用SetValue()来进行设置属性,老方法已经不再适用。原因在第4小节中已经提到过,WPF属性系统可能直接调用SetValue()方法而跳过我们定义的验证代码。
作为代替,WPF提供了两种方法来防止产生非法值:
1.ValidateValueCallback 此函数可以接受或者拒绝新值。通常被用来捕获违反属性约束的明显错误。在DependencyProperty.Register()方法中有一个参数提供了该回调函数。
2.CoerceValueCallback 此函数可以将新值修改为更容易被接受的值。通常用来处理相同对象设置的依赖属性值相冲突的问题。这些值本身都是合法的,但是它们本身不相 容。此方法在FrameworkPropertyMetadata对象的构造函数中作为一个参数。
调用顺序:
在初始化时,调用ValidateValueCallback 。
在值被修改时,先调用ValidateValueCallback,然后调用CoerceValueCallback,当两者都正常时,再调用响应函数PropertyChangedCallback。
如果有父类存在,会先调用父类的ValidateValueCallback,再调用子类的ValidateValueCallback,但是强制回调CoerceValueCallback只有子类的被调用。
为了帮助理解记忆,我写了一个小例子,代码比较少,直接贴出来了
public class test:DependencyObject
{
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(test), new UIPropertyMetadata(0, PropertyChanged, CoerceValue), ValidateValue);
static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show(String.Format("PropertyChanged - 属性:{0} 新值:{1} 旧值:{2}", e.Property.Name, e.NewValue, e.OldValue));
}
//返回强制转换后的值
static object CoerceValue(DependencyObject dobj, object newValue)
{
MessageBox.Show(String.Format("CoerceValue - {0}", newValue));
return newValue;
}
static bool ValidateValue(object obj)
{
MessageBox.Show(String.Format("ValidateValue - {0}", obj));
return true;
}
}
在主函数中实例一个对象
public MainWindow()
{
InitializeComponent();
test a = new test();
a.MyProperty = 10;
}
运行结果顺序如下:
.
可以看到有两次的ValidateValue,应该是new test()时就调用了一次ValidateValue,然后当属性值发生改变时又调用了一次ValidateValue。符合6小节中提到的调用顺序。