依賴項屬性的重寫html
在基於C#的編程中,對屬性的重寫經常是一種行之有效的解決方案:在基類所提供的屬性訪問符實現不能知足當前要求的時候,咱們就須要從新定義屬性的訪問符。編程
但對於依賴項屬性而言,屬性執行邏輯的從新定義並不能存在於CLR屬性包裝中:WPF內部對依賴項屬性的實現要求依賴項屬性的CLR包裝實現僅僅調用GetValue()以及SetValue()屬性,而不能提供其它的自定義邏輯。相反地,咱們須要經過更改建立時所傳入的元數據來指定自定義屬性執行邏輯,甚至在某些更苛刻的要求下,如更改依賴項屬性的類型,從新定義一個具備相同名稱的依賴項屬性。安全
對一個依賴項屬性的重寫很是簡單。若是一個類型從其基類中繼承了一個依賴項屬性,那麼軟件開發人員能夠在派生類中經過OverrideMetadata()方法完成對屬性元數據的覆蓋。在重寫元數據的時候,系統會將新的元數據與以前的依賴項屬性元數據中的各信息進行合併或替換:ide
1) 合併PropertyChangedCallback。函數
2) 替換DefaultValue。this
3) 替換CoerceValueCallback的實現。spa
4) 合併ValidationCallback。code
5) 對於FrameworkPropertyMetadata而言,FrameworkPropertyMetadataOptions的標誌組合爲按位或運算。htm
這裏所提到的操做主要分爲兩種:合併和替換。在這裏,合併的意思就是在類型的繼承層次中的全部對該組成的賦值都將會被保留。在須要執行該組成的時候,WPF屬性系統會按照類型的繼承層次依次調用該組成。而替換則表示當前對該組成的聲明將會徹底替換其全部基類中所聲明的該組成。接下來,WPF屬性系統僅僅會調用類型繼承層次中最高層次的類型所聲明的該組成。對象
如今咱們來看看元數據中各個組成採起合併或是替換的理由。首先要討論的就是PropertyChangedCallback。該回調所作的事情就是在一個依賴項屬性發生了更改的時候刷新其它該類型所包含的依賴項屬性。固然,這種邏輯在依賴項屬性聲明的類型中實現是最正常的一種想法:在同一個類型中調用該函數,刷新其它依賴項屬性值能夠保證該類型實例處於正常的狀態。
在屬性發生更改的時候,系統將首先調用最高層次派生類中所設置的PropertyChangedCallback回調,並沿類型層次結構依次調用各基類實現所提供的各個回調。就像前幾節中的實例代碼所展現的那樣,這些回調經常經過調用CoerceValue來完成其它相關聯依賴項屬性的更新。經過這一系列回調,該類型繼承層次中的各個類型都將處於一個正常的狀態。
對DefaultValue的替換則很是容易理解:因爲一個屬性不能同時擁有多個默認值,所以使用新的默認值替換基類中所聲明的默認值是一種很是正常的選擇。
接下來則是CoerceValueCallback。在依賴項屬性發生變化的時候,屬性系統將僅調用最直接元數據的CoerceValueCallback。這是由於基類中的CoerceValueCallback回調並不瞭解派生類中的各個屬性,所以一旦定義了新的CoerceValueCallback回調,基類中所定義的邏輯將再也不適合對依賴項屬性的值進行約束。
下一個須要討論的組成則是ValidationCallback回調。因爲該函數是在屬性註冊時傳入的,而不是做爲元數據中所儲存的數據存儲在屬性系統中。所以它沒法被新的屬性註冊所覆蓋。同時不將其添加到元數據中的理由:萬一覆蓋了,那還須要將全部原ValidationCallback回調中的邏輯重寫一遍。
最後一個須要說明的則是元數據選項的處理。在經過OverrideMetadata()方法操做一個元數據所記錄的各個元數據選項的時候,全部的元數據選項將被合併。固然,這裏有一種狀況就是消除以前設置的元數據選項。在須要達到該目的的時候,咱們須要將該元數據選項所對應的屬性設置爲false。舉例來講,軟件開發人員能夠在元數據中經過NotDataBindable標記設置一個依賴項屬性不能被綁定。可是若是須要經過OverrideMetadata()函數清除該選項的時候,軟件開發人員就須要在傳入的元數據上將IsNotDataBinable屬性設置爲false。
固然,OverrideMetadata()函數僅僅是一種重用原有依賴項屬性的方法。另外一種重用的方法則是AddOwner()。該函數將其它類型中的依賴項屬性添加到當前類型中。該函數的簽名以下:
public DependencyProperty AddOwner(Type ownerType, PropertyMetadata typeMetadata);
該函數用來將一個DependencyProperty添加到ownerType所表示的類型上,並能夠經過typeMetadata更改該依賴項屬性的行爲。
在使用標示依賴項屬性的DependencyProperty類型的標記時,咱們最好使用AddOwner()函數所返回的依賴項屬性標記,而不是原註冊類型中所保存的依賴項屬性標記。這樣作的最主要目的更可能是基於語義的考慮。實際上,經過本來的依賴項屬性標記以及AddOwner()所返回的依賴項屬性標記進行操做所返回的運行結果是相同的。
與OverrideMetadata()函數明顯不一樣的是,該函數並不繼承原屬性的元數據。所以在使用AddOwner()函數時,軟件開發人員最須要考慮的事情就是是否須要自行指定新屬性的元數據。固然,若是軟件開發人員對基類的依賴項對象調用AddOwner,那麼元數據將被繼承並和新元數據合併。
引用類型的依賴項屬性
實現一個引用類型的依賴項屬性與實現普通的依賴項屬性的步驟並無什麼不一樣:定義一個CLR屬性包裝,並在該屬性包裝中經過GetValue()以及 SetValue() 函數完成對依賴項屬性值的獲取和設置。惟一一點不一樣的是,軟件開發人員不該該在依賴項屬性註冊的時候爲該依賴項屬性提供一個默認值,而是在類型的初始化函數中爲該依賴項屬性顯式地賦值。
爲何要這樣作呢?這是由於在這種狀況下,多個實例上的引用類型依賴項屬性可能會返回一個相同的引用類型實例。產生該問題的緣由是由依賴項屬性的兩個特性共同做用產生的:1. 在沒有通過賦值的狀況下,一個依賴項屬性所返回的值就是在依賴項屬性註冊時傳入的默認值。2.在依賴項屬性註冊過程當中所傳入的值其實是引用類型實例的引用,並將做爲全部該依賴項屬性的默認值,指向同一個引用類型實例。
所以在實現一個引用類型的依賴項屬性時,咱們須要在構造函數中顯式地爲該引用類型的依賴項屬性賦值。在這種狀況下,您有兩種選擇:首先查看依賴項屬性的類型是否自定義類型,並能夠由class更改爲爲struct。若是不能,那麼在依賴項屬性註冊過程當中將默認值標爲null,而在構造函數中再將其設置爲所須要的默認值。
第一種方法在WPF實現中很是常見。就以Control類的Padding屬性爲例:
public static readonly DependencyProperty PaddingProperty = DependencyProperty .Register("Padding", typeof(Thickness), typeof(Control), new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.AffectsParentMeasure));
上面的代碼註冊了一個類型爲Thickness的依賴項屬性PaddingProperty,並在該屬性的元數據中傳入了一個默認值。在查看Thickness類型的定義後能夠發現,其其實是一個結構體。在C#中,結構體會在棧上被分配,從而避免了多個該屬性所在UI元素引用同一個引用類型實例的狀況。
但事情不能老是這麼幸運。首先,依賴項屬性的類型可能並非一個用戶自定義類型,所以咱們並無機會將其轉化爲結構體。另外,一個類型所包含的信息可能很是多,在那種狀況下,將一個類型實現爲結構體是並不合適的。所以在必須建立一個引用類型的依賴項屬性時,咱們須要在構造函數中對該屬性分別賦值。例如ItemsControl就提供了一個ItemsPanel依賴項屬性。若是該依賴項屬性經過構造函數進行初始化,那麼建立依賴項屬性的函數調用以及構造函數定義將以下代碼所示:
public static readonly DependencyProperty ItemsPanelProperty = DependencyProperty .Register("ItemsPanel", typeof(ItemsPanelTemplate), typeof(ItemsControl), new FrameworkPropertyMetadata(null, ……)); public ItemsControl() { SetValue(ItemsPanelProperty, new StackPanel()); }
可是這違反了WPF對於依賴項屬性容器類型構造函數定義的最佳實踐。在一個依賴項屬性的註冊過程當中,以及在派生類對該屬性的覆蓋過程當中,軟件開發人員均可覺得依賴項屬性設置回調函數。同時在每次依賴項屬性發生變化的時候,這些回調函數都將被執行。因爲這些回調函數是在基類的構造函數中被觸發,但其所調用的函數可能被派生類重寫,因此這些函數的執行可能處於派生類並無徹底初始化的狀況。
爲了不這種問題,WPF提出了一個定義安全的構造函數的標準:
1. 爲您的類型提供一個默認構造函數:
public MyClass : SomeBaseClass {
public MyClass() : base() {
// 全部成員的初始化,包括其它構造函數可能賦值的數據成員或回調函數
// 將會使用的數據成員
}
}
2. 若是一個類型提供了非默認構造函數,那麼該構造函數首先須要調用該類型的默認構造函數,而後再使用SetValue()等函數設置各依賴項屬性的值。
public MyClass : SomeBaseClass {
public MyClass(object toSetProperty1) : this() {
// 注意,這裏調用的是默認構造函數,而不是基類的構造函數
Property1 = toSetProperty1;
}
}
只是誰又能保證用戶都熟知這些規則並在編寫自定義類型的時候按照這些規則對類型進行編寫呢?
若是依賴項屬性的類型是一個集合,那麼另一點須要注意的地方則是:XAML解析器沒法知道如何調用一個泛型函數。也就是說,若是一個依賴項屬性的類型是List<T>,那麼WPF並不知道如何調用List<T>.Add(T item),而只知道如何調用非泛型接口成員。所以可知若是軟件開發人員但願一個屬性是一個集合,那麼該集合類型須要實現非泛型的IList接口,如Collection<T>或List<T>。
而在實現一個集合類型的屬性時,究竟是將其實現爲一個只讀依賴項屬性仍是可讀寫依賴項屬性則會影響該屬性在XAML中的使用方法。就如下面兩種XAML標記爲例:
<Toolbar> <Toolbar.Items> <ToolbarItem .../> </Toolbar.Items > </Toolbar> <Toolbar> <Toolbar.Items> <ToolbarItemCollection> <ToolbarItem/> </ToolbarItemCollection> </Toolbar.Items> </Toolbar>
固然,上面的XAML代碼僅僅是用做示例,而並不是是實際的WPF代碼。假設這裏的Toolbar類型擁有一個Items屬性,其用來記錄全部的ToolbarItem類型的子元素。在XAML分析第一段XAML的時候,WPF將首先調用Toolbar.Items屬性的get訪問符,並依次將該段XAML中所聲明的子元素添加到Items屬性所記錄的集合中。而在分析第二段XAML的時候,WPF將首先建立一個ToolbarItemCollection,並將全部的子元素添加到該集合之中。在該集合建立完畢以後,WPF將調用Toolbar.Items屬性的set訪問符,以將該集合設置爲Toolbar.Items屬性的值。
轉載請註明原文地址:http://www.cnblogs.com/loveis715/p/4343374.html
商業轉載請事先與我聯繫:silverfox715@sina.com,我只會要求添加做者名稱以及博客首頁連接。