WPF依賴屬性(續)(2)依賴屬性與附加屬性的區別
接上篇,感謝各位的評論,都是認為依賴屬性的設計並不是為了節省記憶體,從大的方面而講是如此.樣式,資料繫結,動畫樣樣都離不開它.這篇我們來看下依賴屬性與附加屬性的區別.
註冊方法
我們知道註冊依賴屬性使用Register方法,註冊附加屬性則使用RegisterAttached方法,如下程式碼
public class DPCustomPeople:DependencyObject { public static readonly DependencyPropertyAgeProperty = 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考慮以上程式碼,你可能從來都沒有試過在沒有附加屬性的情況下,下面的只有Get,Set的方法,然後嘗試在XAML中設定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); } }
<Button local:AttachEntity.Width="300" Content="Button" />
即來看到這裡,我們不妨試試依賴屬性
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);returnRegisterCommon(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
經過上面的推敲,我們可以看到依賴屬性與附加屬性的區別在於屬性元資料的變化,附加屬性也變的不再那麼神奇了.下面歡迎你加入討論中來.