1. Dependency Property

WPF里对.net中的属性进行了包装,WPF里用到的绝大部分属性都是Dependency property。

其实现大体如下:

//NOTE: This file is not meant to be compiled!  

 

public class Button : ButtonBase  

{  

    // 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) { }  

}

注意,DependencyProperty被声明为static readonly,那它就拥有唯一实例,为什么界面上有n个button,只对应一个IsDefaultProperty,而又可以正常工作呢,原来在注册dependency property时,wpf内部作了一些额外的事情,大体描述如下,可能不严格:

wpf内部,每个dependency property对应一个hash表,键是每个实例的唯一标识,值就是dependency property对应的值了。在new一个实例时,会生成一个唯一标识。

值得注意的是,wpf内部代码都是用GetValue和SetValue进行取/赋值操作,不是根据自己包装的属性也就是这个例子中的IsDefault,包不包装属性,对于wpf是不关心的,所以我们自己的对外属性的get,set中千万不要加入其他代码,以免出现预期结果和wpf执行结果不一致的情况。

Dependency property有什么好处或用处呢?

减少每个实例中属性占用的空间?不尽然,从效果上,并不明显,毕竟值都是要保存的。

MSDN上摘录如下:

The purpose of dependency properties is to provide a way to compute the value of a property based on the value of other inputs. These other inputs might include system properties such as themes and user preference, just-in-time property determination mechanisms such as data binding and animations/storyboards, multiple-use templates such as resources and styles, or values known through parent-child relationships with other elements in the element tree. In addition, a dependency property can be implemented to provide self-contained validation, default values, callbacks that monitor changes to other properties, and a system that can coerce property values based on potentially runtime information. Derived classes can also change some specific characteristics of an existing property by overriding dependency property metadata, rather than overriding the actual implementation of existing properties or creating new properties.

依赖项属性的用途在于提供一种方法来基于其他输入的值计算属性值。这些其他输入可以包括系统属性(如主题和用户首选项)、实时属性确定机制(如数据绑定和动画/演示图板)、重用模板(如资源和样式)或者通过与元素树中其他元素的父子关系来公开的值。另外,可以通过实现依赖项属性来提供独立验证、默认值、监视其他属性的更改的回调以及可以基于可能的运行时信息来强制指定属性值的系统。派生类还可以通过重写依赖项属性元数据(而不是重写现有属性的实际实现或者创建新属性)来更改现有属性的某些具体特征。

可以说,wpf里所有常用的特性都是基于dependency property的。

覆写metadate

public class SpinnerControl : ItemsControl
{
    static SpinnerControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(SpinnerControl),
            new FrameworkPropertyMetadata(typeof(SpinnerControl))
        );
    }
}

在从最初注册依赖项属性的类派生时,可以通过重写依赖项属性的元数据来更改该属性的某些行为。对元数据的重写依赖于 DependencyProperty 标识符。重写元数据不需要重新实现属性。元数据的变化是由属性系统在本机处理的;对于所有从基类继承的属性,每个类都有可能基于每个类型保留元数据。

2. Attached Property:

Attached Property实际上是Dependency Property的一种用法,利用了Wpf属性系统中的hash 表,如果指定的dependency property不在这个类的dependency property hash 表中,就加到该类的hash表中。(Setvalue 时会做这件事情)

这样就可以实现三十六计中的“无中生有”计了,哈哈,一个对象,本来没有的属性,我们可以“生”出一个出来,请看下面的例子(部分代码来自Wpf unleashed,不错的书:

    <DockPanel>

        <Label DockPanel.Dock="Left">Left</Label>

        <Label DockPanel.Dock="Right">Right</Label>

    </DockPanel>

    <StackPanel TextElement.FontSize="30" TextElement.FontStyle="Italic" 

      Orientation="Horizontal" HorizontalAlignment="Center">

            <Button MinWidth="75" Margin="10">Help</Button>

            <Button MinWidth="75" Margin="10">OK</Button>

    </StackPanel>

这里的DockPanel.Dock="Left",DockPanel.Dock="Right", TextElement.FontSize="30", TextElement.FontStyle="Italic"就是attached property,原本Label,StackPanel没有Dock和FontSize,FontStyle属性,我们想加入这些属性怎么办呢,强行加入这些属性就行了,有点变态,当然要强行加入新属性,就像Dock="Left"一样,但是这样的DockProperty不能被wpf认出来,因为它不知道是哪个类是注册了DockProperty对象,(没注册的DependencyProperty不能使用),所以我们要指定是DockProperty是在DockPanel中注册的,那么就应该写成DockPanel.Dock="Left"。

同样的需要指定TextElment来修饰FontSize,FontStyle,也可以把TextElement改为TextBlock,因为TextElement中也有FontSize和FontStyle两个denpendency property,(TextBlock的FontSize,FontStyle实际上就是来自于TextElement,是一样的)。

现在有个问题,我们可以把任意的dependency property加到一个对象上,那么如果这个对象不支持这个denpendency property会怎么样?

答案是什么都不会发生,只有在支持该dependency property的对象上才会有效果。

再深入一点,从上面的例子可以看出,TextElement是定义FontSize和FontStyle的类,其他类,比如Button,是怎么使用FontStyle和FontSize呢?用DependencyProperty.AddOwner方法,如下:

Control.FontSizeProperty = TextElement.FontSizeProperty.AddOwner(

                typeof(Control), new FrameworkPropertyMetadata(SystemFonts.MessageFontSize,

                FrameworkPropertyMetadataOptions.Inherits));

这样,所有控件都支持FontSize属性,而且是TextElement中的FontSizeProperty。

下面看看如何定义一个Attached Property:

TextElement.FontSizeProperty = DependencyProperty.RegisterAttached(

"FontSize", typeof(double), typeof(TextElement), new FrameworkPropertyMetadata(

                    SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits |

                    FrameworkPropertyMetadataOptions.AffectsRender |

                    FrameworkPropertyMetadataOptions.AffectsMeasure),

                    new ValidateValueCallback(TextElement.IsValidFontSize));

代码和注册Dependency Property的很像,实际上两段代码功能也基本一样,只是RegiserAttached简化一了下。

如果我们要实现自己的类,什么时候用dependency property,什么时候用attached property呢?

如果只是这个类自己使用的属性,可以向外开放dependency property

如果不确定这个类的children是否有需要的denpendency property,而在某些时候需要children设置这样的property时,用attached property,典型的是用在构建UI时的那些Panel。