依赖属性
简介:
当你开始用WPF编程的时候,很快就会碰到“依赖属性”。它们和一般的.Net属性看起来很相似,但简单概念之后则是更复杂和更强大。
主要的区别在于:平常的.NET属性的值直接读取于类的一个私有属性,而依赖属性的值则是通过调用继承自DependencyObject的GetValue()方法动态赋值的。
当你给一个依赖属性赋值时,它不是存储在对象的字段中,而是在存储在基类DependencyObject提供的一个键-值配对的字典中。一条记录中的键(Key)就是该属性的名称,而值(Value)则是想要设置的值。
依赖属性的优点:
-
节约内存的使用
在你认为超过90%的用户界面控件的属性通常留其初始值时,为每一个属性存储一个字段将是对内存的巨大的浪费。依赖属性解决了仅仅存储改变了属性的问题。默认值在依赖属性中只存储一次。
-
值继承
当你访问一个依赖属性时要使用一个值解决策略。如果当前没有值需要设置,则依赖属性会遍历整个逻辑树直至它找到一个值。当你在根元素中设置字体大小时,它会应用于所有文本块,除非你重写这个值。
-
修改通知
依赖属性有一个内嵌的修改通知机制。当属性的值被改变后,通过在属性元数据注册一个的回调函数就能得到修改的通知。同样也可以用在数据绑定中。
值访问策略
每次访问一个依赖属性,它内部会按照下列的顺序由高到底处理该值。它首先确认自身的值是否可用,如果不可用,则会触发一个自定义的样式触发器……继续直到它找到一个值。最后默认值经常是可用的。
背后的秘密
每一个WPF控件注册了一系列的依赖属性在静态的DependencyProperty类中。他们由一个键值(对每个类都是唯一的)和包含默认值和回调函数的原数据组成。
所有要使用依赖属性(DependecyProperties)的类型都必须由DependencyObject来驱动。这个基类定义了一个键值字典来存储依赖属性的所有值。记录中的键则是依赖属性主要申明的。
当你通过.Net属性包装器访问依赖属性的时候,它在内部会调用一个方法GetValue(DependencyProperty)来访问值。下图详细的解释了该方法使用的值解决策略。如果当前值是可用的,那么就直接从字典(Dictionary)里读出该值。如果没有值被设定,它将上升到整个逻辑树,寻找一个可继承的值。如果没有找到可继承的值,它将被设置为在属性原数据中定义好的默认值。下面的序列有点简化,但它显示了主要的思想。
创建依赖属性
创建一个依赖属性,需添加一个DepdencyProperty类型的静态字段,并调用DependencyProperty.Register()来创建一个依赖属性的实例。依赖属性一般需要以…Property结尾。这样符合PWF的命名规范。
为了使依赖属性像一般的.Net属性一样被访问,则需要添加一个属性包装器。该包装器除了通过继承自DependencyObject的GetValue()和SetValue()方法,以DependencyProperty作为键值来设置和读取它的值外不做任何事情。
强调:不要在这些属性中添加任何逻辑,因为它们仅仅当在代码中赋值的时候才被调用。而在XAML中可直接调用SetValue方法设定属性。
如果你使用的是Visual Studio,你可以输入propdp在敲两次tab键来自动创建一个依赖属性。
1 // Dependency Property
2 public static readonly DependencyProperty CurrentTimeProperty =
3 DependencyProperty.Register( "CurrentTime", typeof(DateTime),
4 typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now));
5
6 // .NET Property wrapper
7 public DateTime CurrentTime
8 {
9 get { return (DateTime)GetValue(CurrentTimeProperty); }
10 set { SetValue(CurrentTimeProperty, value); }
11 }
每一个依赖属性都提供为修改通知,值的强制转换和验证提供了一些回调函数,这些函数须在依赖属性中注册。
new FrameworkPropertyMetadata( DateTime.Now,
OnCurrentTimePropertyChanged,
OnCoerceCurrentTimeProperty ),
OnValidateCurrentTimeProperty );
值的修改回调:
修改回调是一个静态方法,任何时候,只要TimeProperty的值改变了,它都会被调用。新的值会在EventArgs参数传输,此次改变的对象则在source里传输。
private static void OnCurrentTimePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
MyClockControl control = source as MyClockControl;
DateTime time = (DateTime)e.NewValue;
// Put some update logic here...
}
值的限制回调:
限制制回调函数允许调整一个值,如果出界,将会抛出一个异常。一个好的例子就是在进度条中设置一个最大上限值或者最小下限值。这种情况下我们可以限定值在允许的范围内。下面的例子我们排出了已经过去了的时间。
private static object OnCoerceTimeProperty( DependencyObject sender, object data )
{
if ((DateTime)data > DateTime.Now )
{
data = DateTime.Now;
}
return data;
}
值的验证回调:
验证回调用来检验要设置的值是否有效。如果返回无效的(False),则抛出一个ArgmentException的异常。我们例子的要求是数值必须是DataTime的实例.
private static bool OnValidateTimeProperty(object data)
{
return data is DateTime;
}
只读的依赖属性:
// Register the private key to set the value
private static readonly DependencyPropertyKey IsMouseOverPropertyKey =
DependencyProperty.RegisterReadOnly("IsMouseOver",
typeof(bool), typeof(MyClass),
new FrameworkPropertyMetadata(false));
// Register the public property to get the value
public static readonly DependencyProperty IsMouseoverProperty =
IsMouseOverPropertyKey.DependencyProperty;
// .NET Property wrapper
public int IsMouseOver
{
get { return (bool)GetValue(IsMouseoverProperty); }
private set { SetValue(IsMouseOverPropertyKey, value); }
}
附加属性:
这个问题的解决方案就是附加属性.它们被定义在一个需要从其他控件的指定内容中获取的数据的控件中.比如一个元素由它的父布局面板布置.
给附加属性赋值,需在XAML中添加一个以该元素开头的属性.如在画布面板中设置Canvas.Top和Canvas.Left来部署一个按钮,你可以这样写:
<Canvas>
<Button Canvas.Top="20" Canvas.Left="20" Content="Click me!"/>
</Canvas>
public static readonly DependencyProperty TopProperty =
DependencyProperty.RegisterAttached("Top",
typeof(double), typeof(Canvas),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.Inherits));
public static void SetTop(UIElement element, double value)
{
element.SetValue(TopProperty, value);
}
public static double GetTop(UIElement element)
{
return (double)element.GetValue(TopProperty);
}
监视依赖属性的改变:
如果想监视依赖属性的改变,可以派生此类,定义它的属性,重写属性的原数据并传递一个PropertyChangedCallBack参数。而一个更简易的方法就是获取DependencyPropertyDescriptor并调用AddValueChange()为其挂接一个回调函数。
DependencyPropertyDescriptor textDescr = DependencyPropertyDescriptor.
FromProperty(TextBox.TextProperty, typeof(TextBox));
if (textDescr!= null)
{
textDescr.AddValueChanged(myTextBox, delegate
{
// Add your propery changed logic here...
});
}
变清除当前值:
因为null也是一个有效的设定值,所以就用DependencyProperty.UnsetValue来表示一个为设定的值。