是的,文章的題目看起來很牛,我認可。html
附加屬性是WPF中的一個很是重要的功能。例如在設置佈局的過程當中,軟件開發人員就經常經過DockPanel的Dock附加屬性來設置其各個子元素所處的佈局位置。一樣地,在爲程序添加一個新的功能時,咱們也經常須要建立自定義的附加屬性來完成該功能。ide
附加屬性簡介函數
首先,咱們要對附加屬性有一個簡單的認識:什麼是附加屬性,而爲何WPF提供了附加屬性呢?佈局
在WPF中,附加屬性用來表示定義在一個類型上,卻能夠在其它特定類型實例上被使用的屬性。因爲該屬性並不是定義在這些實例所對應的類型之上,而更像是爲這些類型額外地添加了一系列值,所以其被稱爲附加屬性。spa
一種較好的理解附加屬性這種行爲的方式則是將其看成一個服務。其中子元素經過設置這些附加屬性的值來定義如何使用服務。而定義附加屬性的類型將根據這些子元素中設置的數值決定其所提供服務的方式,如一個控件應該放置在哪裏。這種使用方法也即是決定咱們是否建立一個附加屬性的最重要因素。.net
而實現一個附加項屬性則很是簡單。首先,軟件開發人員須要經過調用DependencyProperty類的RegisterAttached()函數在屬性系統中聲明一個附加屬性。與依賴項屬性不一樣的是,因爲其並不是做用於定義該附加屬性的類型上,所以沒法提供CLR屬性包裝,而是提供了以Get-以及Set-做爲屬性名前綴的專用方法。這些專用方法中,軟件開發人員能夠經過規定參數以及返回值的類型等方式限制使用該附加屬性的類型以及該屬性的值。code
如今就以.net源碼中的DockPanel.Dock附加屬性的實現來熟悉建立附加屬性的過程:htm
public static readonly DependencyProperty DockProperty = DependencyProperty .RegisterAttached("Dock", typeof(Dock), typeof(DockPanel), ……); public static Dock GetDock(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (Dock) element.GetValue(DockProperty); } public static void SetDock(UIElement element, Dock dock) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(DockProperty, dock); }
首先,軟件開發人員須要經過DependencyProperty類的RegisterAttached()以及RegisterAttachedReadonly()函數來向屬性系統中註冊附加屬性。這兩個函數所使用的各個參數與Register()函數所使用的各個參數相似:經過參數name指定附加屬性的名稱;經過propertyType參數指定附加屬性的類型;經過ownerType參數指定附加屬性所在的類型;經過defaultMetadata參數指定附加屬性所使用的元數據;最後經過validateValueCallback指定驗證邏輯。而在附加屬性的承載類型中,軟件開發人員須要經過一個靜態公有的DependencyProperty來記錄這些新註冊的附加屬性。這個附加屬性的屬性名須要遵照必定的規則:其須要由在調用RegisterAttached()函數時所傳入的參數name以及後綴-Property組合而成。接下來,軟件開發人員就須要定義用來訪問附加屬性的讀寫函數。這些函數的名稱則須要經過前綴Get-和Set-以及附加屬性註冊時所傳入的參數name共同組成。在這些函數內部,軟件開發人員只須要調用DependencyObject的GetValue()和SetValue()便可。blog
讓咱們來回想一下WPF中DockPanel的使用過程:軟件開發人員首先在XAML中聲明瞭一個DockPanel,並在該其中聲明瞭衆多的子元素,以表示須要承載在該DockPanel中的界面組成。在這些界面組成的聲明中,咱們可使用DockPanel.Dock屬性來指定每一個子元素須要放置在DockPanel的哪一個位置。整個過程以下面代碼所示:element
<DockPanel LastChildFill="True"> <Border DockPanel.Dock="Top" ...> <TextBlock Foreground="Black">Dock = "Top"</TextBlock> </Border> ... </DockPanel>
在XAML編譯器遇到對DockPanel.Dock屬性的賦值時,其會將該賦值轉化爲對DockPanel.SetDock()函數的調用。該函數會以包含DockPanel.Dock屬性賦值的界面元素以及被賦予的值做爲參數。而在執行佈局計算的時候,DockPanel會使用GetDock()函數將全部子元素的DockPanel.Dock附加屬性值讀取出來,並根據這些值安排各個子元素所處的位置。這就至關於DockPanel提供佈局計算的服務,而每一個子元素則經過附加屬性DockPanel.Dock提供了自身所須要的服務的類型。
另外須要強調的一點則是附加屬性的Get-和Set-函數中對能使用附加屬性的類型的限制。在DockPanel所提供的GetDock()和SetDock()函數中,第一個屬性的類型是UIElement,也就表示DockPanel只爲UIElement提供附加屬性的服務。這其實也是附加屬性限制其所可施行類型的最經常使用方法:在Get-和Set-函數的定義中將第一個參數的類型設置爲附加屬性的目標類型,從而限制其它類型對該附加屬性的使用。
同理,軟件開發人員也能夠爲Get-函數的返回值以及Set-函數的參數value指定一個特定的類型,以防止用戶代碼爲附加屬性設置一個其它類型的值。其內部實現會直接調用傳入的參數的GetValue()和SetValue()函數來完成附加屬性的設置。這兩個函數並無以自身實例做爲參數。也就是說,附加屬性並無設置在聲明該附加屬性的類型實例上,而是設置在了使用附加屬性的實例上。
從上面的講解中能夠看出,附加屬性所對應的Get-和Set-函數內部並無引用任何宿主類型成員,而是將屬性值設置到了傳入的參數上。所以在定義一個附加屬性的時候,依賴項屬性的宿主類型沒必要從DependencyObject類派生。
若是須要讓附加屬性所對應的服務可以正確執行,僅僅限制附加屬性的目標類型是不夠的。附加屬性所提供的服務經常須要按照必定的方式搜索其所在類型之下的各個子元素,以蒐集它們所包含的有關服務的信息,並根據這些信息提供服務。就以DockPanel類所提供的DockPanel.Dock附加屬性爲例:
protected override Size ArrangeOverride(Size arrangeSize) { UIElementCollection internalChildren = base.InternalChildren; int count = internalChildren.Count; for (int i = 0; i < count; i++) { UIElement element = internalChildren[i]; if (element != null) { Size desiredSize = element.DesiredSize; Rect finalRect = … // 子元素最終所處的佈局位置 switch (GetDock(element)) { case Dock.Left: x += desiredSize.Width; finalRect.Width = desiredSize.Width; break; case Dock.Top: // 依次處理Dock.Top,Dock.Right以及Dock.Bottom等狀況 } element.Arrange(finalRect); } } return arrangeSize; }
上面的示例代碼很是好地展現了一個附加屬性是如何在類型中被使用的:在一般狀況下,包含附加屬性的類型經常包含了一個集合,以記錄其所包含的各個子元素。在特定邏輯運行過程當中,該集合內所記錄的全部子元素將被遍歷。在每次遍歷中,執行邏輯都會取得子元素的附加屬性值,並以此屬性值來決定子元素的具體行爲。
所以,若是這些信息並無按照正確的方式提供,那麼附加屬性所提供的服務也可能不被正確運行。就如下面的代碼爲例:
<DockPanel LastChildFill="True"> <Border DockPanel.Dock="Top" ...> <TextBlock DockPanel.Dock=」Bottom」 Foreground="Black">Dock = "Top"</TextBlock> </Border> ... </DockPanel>
在上面的代碼中,DockPanel的Border類型的子元素以及Border所包含的TextBlock元素都設置了DockPanel.Dock附加屬性。可是因爲DockPanel僅僅排列其所包含的各個子元素,而這些子元素所各自包含的內嵌元素的佈局則是由各個子元素本身決定,所以DockPanel僅僅對它的直接子元素的DockPanel.Dock附加屬性進行處理。因此在上面的例子中,TextBlock元素所設置的DockPanel.Dock附加屬性將不會被處理。
附加屬性相關特性
如今來想一想附加屬性在XAML中的使用。在編寫XAML標記的時候,Visual Studio會根據當前輸入提示可用的各屬性。這其中就有附加屬性:
可是這裏就出現了問題:該列表中並無DockPanel.Dock附加屬性,卻只有Grid相關的附加屬性。這是爲何呢?查看有關附加屬性的實現時,咱們發現了一些端倪:
[AttachedPropertyBrowsableForChildren] public static int GetColumn(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (int) element.GetValue(ColumnProperty); }
在上面的代碼中,咱們能夠看到Column附加屬性的Get-訪問函數上添加了一個特性AttachedPropertyBrowsableForChildren。查看MSDN能夠知道,該屬性用來標示當前附加屬性能夠在邏輯樹中的子元素中被使用。其包含兩種不一樣的子元素查詢方法:在沒有顯式地將IncludeDescendants屬性標爲true的時候,依賴項屬性將僅僅在子元素中可見。而在將IncludeDescendants屬性標爲true的時候,該依賴項屬性能夠在相應元素中的任何子元素中出現。通常狀況下,該特性只在Get-訪問符上施行。
在定義一個附加屬性的時候,咱們的確能夠經過標示這些特性使得一個附加屬性能夠在一個元素的屬性列表中出現。可是該附加屬性的設置是否被處理則是由附加屬性的實際執行邏輯所決定的。所以軟件開發人員在使用該特性使一個附加屬性在某個元素中出現的時候,您首先須要儘可能保證處理邏輯能處理該特性所標示的附加屬性可見範圍。
另外一個較爲有用的特性就是AttachedPropertyBrowsableForType。該特性容許軟件開發人員指定一個附加屬性只對特定類型可見。在須要令附加屬性對多種類型可見的時候,軟件開發人員須要在附加屬性的Get-訪問符上添加多個該類型的特性。
而最後一個特性就是AttachedPropertyBrowsableWhenAttributePresentAttribute。該特性表示只有宿主類型上標明瞭特定特性時,該依賴項屬性纔可見。這並非一個很是經常使用的特性,所以咱們將不在這裏對其進行講解。
附加屬性的綁定
從上面的講解中您已經瞭解到,附加屬性實際上就是一個依賴項屬性,所以其一樣能夠做爲綁定的數據源使用。可是此時綁定所使用的標記則與普通的依賴項屬性有所不一樣。例如在須要綁定到靜態實例local:StaticClass的Button屬性所設置的Grid.Row附加屬性的時候,軟件開發人員須要使用圓括號將附加屬性括起:
{Binding Source={x:Static local:StaticClass}, Path=Button.(Grid.Row)}
使用不一樣標記的緣由則很是簡單:XAML解析器須要根據不一樣的標記來判斷綁定表達式中的各個組成究竟是對依賴項屬性仍是附加屬性的引用。
轉載請註明原文地址:http://www.cnblogs.com/loveis715/p/4343381.html
商業轉載請事先與我聯繫:silverfox715@sina.com,我只會要求添加做者名稱以及博客首頁連接。