1. 程式人生 > >WPF依賴屬性(續)(2)依賴屬性與附加屬性的區別

WPF依賴屬性(續)(2)依賴屬性與附加屬性的區別

原文: WPF依賴屬性(續)(2)依賴屬性與附加屬性的區別

 

     接上篇,感謝各位的評論,都是認為依賴屬性的設計並不是為了節省記憶體,從大的方面而講是如此.樣式,資料繫結,動畫樣樣都離不開它.這篇我們來看下依賴屬性與附加屬性的區別.

註冊方法

我們知道註冊依賴屬性使用Register方法,註冊附加屬性則使用RegisterAttached方法,如下程式碼

public class DPCustomPeople:DependencyObject
{   
    public static readonly DependencyProperty 
AgeProperty = DependencyProperty.Register("Age", typeof(int), typeof(DPCustomPeople)); public static readonly DependencyProperty Age2Property = DependencyProperty.RegisterAttached("Age2", typeof(int), typeof(DPCustomPeople)); }

包裝屬性

public int Age
{
    get { return (int
)GetValue(AgeProperty); } set { SetValue(AgeProperty, value); } } public int Age2 { get { return (int)GetValue(Age2Property); } set { SetValue(Age2Property, value); } }

一般預設依賴屬性使用CLR屬性進行包裝,附加屬性使用Get,Set方法進行包裝.
下面我則均以屬性進行包裝,從表面上看兩者除了方法不同,其他都是一樣的
那麼附加屬性的魔力到底何在呢?

XAML的魔力

public class 
AttachEntity { public static double GetWidth(DependencyObject obj) { return (double)obj.GetValue(Button.WidthProperty); } public static void SetWidth(DependencyObject obj, double value) { obj.SetValue(Button.WidthProperty, value); } }
考慮以上程式碼,你可能從來都沒有試過在沒有附加屬性的情況下,下面的只有Get,Set的方法,然後嘗試在XAML中設定
<Button local:AttachEntity.Width="300" Content="Button" />

奇蹟出現,賦值成功,所以我們一直以來都誤解了附加屬性.
image

即來看到這裡,我們不妨試試依賴屬性

public static readonly DependencyProperty AgeProperty =
    DependencyProperty.Register("Age", typeof(int), typeof(DPCustomPeople),
    new UIPropertyMetadata(0,(sender,args)=>
                                 {
                                     var element = sender as DPCustomPeople;
                                 }));

public static int GetAge(DependencyObject obj)
{
    return (int)obj.GetValue(AgeProperty);
}

public static void SetAge(DependencyObject obj, int value)
{
    obj.SetValue(AgeProperty, value);
}

<Button local:DPCustomPeople.Age="2" Content="Button" Name="button1"/>

程式執行正常,發生下列情況

  • 可以取到附加屬性值
  • 屬性變更通知沒有發生

預設屬性元資料

現在是時候來看看依賴屬性與附加屬性的區別了,以下是內部註冊方法

第一段:註冊依賴屬性的方法

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
{
    
 
  RegisterParameterValidation(name, propertyType, ownerType);
 
    PropertyMetadata defaultMetadata = null;
    if ((typeMetadata != null) && typeMetadata.DefaultValueWasSet())
    {
        defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue);
    }
    
 
  DependencyProperty property = 
  RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
 
    if (typeMetadata != null)
    {
        property.OverrideMetadata(ownerType, typeMetadata);
    }
    return property;
}

第二段:註冊附加屬性方法

public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
   
  
  RegisterParameterValidation(name, propertyType, ownerType);
 
    return 
 
  RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
 
}


請先忽略上面劃線部分的程式碼,現在我們看到在註冊依賴屬性時,前後多了一些處理.

原來是屬性元資料在作怪。

註冊依賴屬性時,會傳入一個屬性元資料,但內部定義了一個預設的屬性元資料(defaultMetadata ),當依賴屬性註冊完畢後,則重寫了屬性元資料(OverrideMetadata),而註冊附加屬性時,則直接傳入引數.這個引數則直接作為了依賴屬性的預設元資料,如下程式碼

var people = new DPCustomPeople();
var defaultMetadata=DPCustomPeople.AgeProperty.DefaultMetadata;
var metadata = DPCustomPeople.AgeProperty.GetMetadata(people);


附加屬性只有預設屬性元資料,根據以上原始碼,我們甚至可以改造附加屬性為依賴屬性,下面的附加屬性則變成了依賴屬性

注意點:
(1)重寫屬性元資料是一個合併的過程,所以重寫的變更事件並不會觸發
(2)若屬性元資料已經註冊完畢,同個型別的屬性元資料不可重複重寫
(3)預設屬性元資料無法更改

public static readonly DependencyProperty Age3Property;
static DPCustomPeople()
{
    var metaData = new PropertyMetadata(0, new PropertyChangedCallback((sender, args) =>
    {
        
        MessageBox.Show("hello1");
    }));
    PropertyMetadata defaultMetadata = null;
    if ((metaData != null))
    {
        defaultMetadata = new PropertyMetadata(metaData.DefaultValue, new PropertyChangedCallback((sender, args) =>
        {
            MessageBox.Show("hello2");
        }));
    }
    Age3Property = DependencyProperty.RegisterAttached("Age3", typeof(int), typeof(DPCustomPeople), defaultMetadata);

    Age3Property.OverrideMetadata(typeof(DPCustomPeople), metaData);
}

屬性元資料重寫的補充

http://www.cnblogs.com/Clingingboy/archive/2010/02/02/1661842.html
在之前有介紹過屬性元資料的部分,這裡做一個補充.上面第三點已經列出來了.
(1)每個DP都會有一個預設屬性元資料,依賴屬性預設屬性元資料由內部建立,其又根據我們傳入的引數,同時建立了一個屬性元資料.也就是說依賴屬性擁有兩個屬性元資料.依賴屬性對於同一物件是無法重寫屬性元資料的.下面則報錯.

            Age3Property = DependencyProperty.Register("Age3", typeof(int), typeof(DPCustomPeople), defaultMetadata);
            Age3Property.OverrideMetadata(typeof(DPCustomPeople), metaData);
            //error

(2)同理,由於附加屬性註冊時只擁有一個預設屬性元資料,所以其初始化時就可以對同類型的物件進行重寫(就是上面的例子)
     注意:重寫屬性元資料時並不會與預設屬性元資料合併,所以附加屬性註冊時,若有回撥方法,總是會觸發的

重寫屬性元資料規則

比如在重寫屬性元資料時重新定義了一個回撥方法,其是一個合併過程,並不會覆蓋父類的回撥方法.如果你想改變重寫規則的話可以重寫

PropertyMetadata的Merge方法,如下則不會觸發父類的回撥方法.這看需求而定

public class CustomPropertyMetadata : PropertyMetadata
{
    public CustomPropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback)
    {
        this.DefaultValue = defaultValue;
        this.PropertyChangedCallback = propertyChangedCallback;
    }
    protected override void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
    {
        var a = this.PropertyChangedCallback;
        
        base.Merge(baseMetadata, dp);
        this.PropertyChangedCallback = a;
    }
}

改寫屬性元資料

預設有兩種方法為一個元素新增ToolTip

<Button ToolTipService.ToolTip="Test">Button</Button>
<Button ToolTip="Test">Button</Button>

兩者效果是相同的,ToolTip其內部還是設定了ToolTipService.ToolTip屬性

public object ToolTip
{
    get
    {
        return ToolTipService.GetToolTip(this);
    }
    set
    {
        ToolTipService.SetToolTip(this, value);
    }
}

注意:改寫屬性元資料並非改寫依賴屬性
參考:http://www.cnblogs.com/yayx/archive/2008/06/03/1213126.html

(1)屬性元資料的改寫 如Control的BackgroundProperty則來自Panel的BackgroundProperty的改寫,下面設定效果相同

this.SetValue(Panel.BackgroundProperty, Brushes.Red);

(2)改寫依賴屬性元資料

ToolTipProperty = ToolTipService.ToolTipProperty.AddOwner(typeof(DPCustomPeople));

其為設定屬性的時候提供了方便,隱藏了ToolTipService的存在,其實不設定並不會怎麼樣.
如內部的ContextMenuProperty

ContextMenuProperty = ContextMenuService.ContextMenuProperty.AddOwner(typeof(DPCustomPeople), new FrameworkPropertyMetadata(null));

下面兩者取屬性是等價的

public ContextMenu ContextMenu
{
    get
    {
        return (GetValue(ContextMenuProperty) as ContextMenu);
    }
    set
    {
        SetValue(ContextMenuProperty, value);
    }
}

public ContextMenu ContextMenu
{
    get
    {
        return ContextMenuService.GetContextMenu(this);
    }
    set
    {
        SetValue(ContextMenuService.ContextMenuProperty, value);
    }
}


那麼改寫屬性元資料到底做了什麼?
改寫實質上一個重寫屬性元資料的過程,區別在OwnerType發生了變化,當OwnerType是繼承關係的話,那麼屬性元資料則進行合併,否則的話則會為該OwnerType建立一個新的屬性元資料.


更新於:2010/8/4

經過上面的推敲,我們可以看到依賴屬性與附加屬性的區別在於屬性元資料的變化,附加屬性也變的不再那麼神奇了.下面歡迎你加入討論中來.