【WPF】依賴屬性
依賴屬性DependencyProperty的誕生背景
WPF開發中,必須使用依賴物件作為依賴屬性的宿主,使二者結合起來。依賴物件的概念被DependencyObject類所實現,依賴屬性的概念則由DependencyProperty類所實現
WPF框架的程式設計經常和介面打交道,經常遇到的一個情況是某個屬性的值的變化會影響到多個其他物件。比如當一個Button的改變大小超過了它的容器,他的容器應該自動調整大小。於是我們考慮在每個屬性的set方法中觸發一些事件,但很快我們發現現有的功能很難滿足我們的需求,至少不能簡潔漂亮的滿足這些需求。
實際上我們的需求更加複雜,WPF中的資料繫結,XAML語法等很多地方都和屬性密切相關,我們迫切需要一種功能更加強大的屬性。
於是在WPF中,引入了一種特殊的屬性,Dependency Property。這種屬性和普通的屬性最大不同在於,它的值的來源並不單一。對這種屬性的取值和賦值都會能與其他物件有影響,因此能得到很大的靈活性
依賴屬性和依賴物件
DependencyObject和DependencyPorperty兩個類是WPF屬性系統的核心。
在WPF中,依賴物件的概念被DependencyObject類實現;依賴屬性的概念則由DependencyPorperty類實現。
必須使用依賴物件作為依賴屬性的宿主,二者結合起來,才能實現完整的Binding目標被資料所驅動。DependencyObject具有GetValue和SetValue兩個方法,用來獲取/設定依賴屬性的值。
DependencyObject是WPF系統中相當底層的一個基類,如下:
繼承樹上可以看出,WPF的所有UI控制元件都是依賴物件。
依賴屬性系統
賴項屬性的應用
WPF中各個功能的實現都離不開依賴項屬性的支援,如繫結,模板,動畫,屬性值的優先順序
動態資源——Background="{DynamicResource MyBrush}"
資料繫結——Background="{Binding MyBrush}"
樣式——<Setter Property="Background" Value="Red"/>
動畫——<ColorAnimation Storyboard.TargetName="MyButton" Storyboard.TargetProperty="Background" From="Red" To="Blue"/>
元資料重寫——通過依賴屬性的OverrideMetadata來重寫父類的屬性預設行為
屬性值繼承——繼承元素樹父級的屬性值(DataContext,FontSize)
WPF設計器整合——使用WPF設計器時,可以在屬性視窗編輯屬性值
命名規則:
欄位總是與屬性同名,但其後面追加了 Property
字尾。 有關此約定及其原因的詳細資訊,
依賴屬性建立
1、輸入快捷鍵 "Propdp" 點選 TAB 按鍵自動生成預設 依賴屬性如下為系統預設依賴屬性
public int MyProperty { get { return (int)GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));
按照自己的需要修改依賴屬性名稱、屬性名稱。
依賴屬性例項並非使用new操作符得到而是使用DependencyProperty.Register方法生成。
DependencyProperty.Register的引數說明
- 第一個引數為int型別,表示指明以哪個CLR屬性作為這個依賴屬性的包裝器。就是程式碼"MyProperty"
- 第二個引數指明此依賴屬性用來儲存什麼樣的值。
- 第三個引數用來指明此依賴屬性的宿主是什麼型別,或者說DependencyProperty.Register方法要將這個依賴屬性註冊到哪個型別上
- 1.依賴屬性包裝器是一個CLR屬性,並不是依賴屬性,沒有包裝器,依賴屬性依舊存在。
- 2.既然沒有包裝器依賴屬性也存在,那麼包裝器是幹什麼用的呢?包裝器的作用是以"例項屬性"的形式向外界暴露依賴屬性,這樣,一個依賴屬性才能成為資料來源的一個Path。
- 3.註冊依賴屬性時使用的第二個引數是一個數據型別,這個資料型別也是包裝器的資料型別。
DependencyProperty.Register帶4個引數,第四個引數的型別是PropertyMetadata類,作用是給依賴屬性的DefaultMetadata屬性賦值。顧名思義,DefaultMetadata的作用就是向依賴屬性的呼叫者提供一些基本資訊,這些資訊包括:
- CoerceValueCallback:依賴屬性的值被強制改變時此委託會被呼叫,此委託可關聯一個函式。
- DefaultValue:依賴屬性未被顯示賦值時,若讀取之則獲得此預設值,不設定此值會丟擲異常。
- IsSealed:控制PropertyMetadata的屬性值是否可以更改,預設值為true。
- PropertyChangeCallback:依賴屬性的值被改變之後此委託會被呼叫,此委託可關聯一個函式。
定義
上面介紹了依賴屬性所帶來的好處,這時候,問題又來了,怎樣自己定義一個依賴屬性呢?C#屬性的定義大家再熟悉不過了。下面通過把C#屬性進行改寫成依賴屬性的方式來介紹依賴屬性的定義。下面是一個屬性的定義:
1 public class Person 2 { 3 public string Name { get; set; } 6 }
在屬性系統中註冊屬性
為使屬性成為依賴屬性,必須在屬性系統維護的表中註冊該屬性,併為屬性指定一個唯一識別符號。此唯一識別符號會用作後續屬性系統操作的限定符。 這些操作可能是內部操作,也可能使用你自己的程式碼呼叫屬性系統 API。 若要註冊屬性,可在類的主體內(在類中但在所有成員定義外)呼叫 Register 方法。 Register 方法呼叫也會提供識別符號欄位作為返回值。 Register 呼叫在其他成員定義外完成的原因在於,需要使用此返回值分配並建立一個 DependencyProperty 型別的 public
static
readonly
欄位,作為類的一部分。 此欄位會作為依賴屬性的識別符號。
在把上面屬性改寫為依賴屬性之前,下面總結下定義依賴屬性的步驟:
- 讓依賴屬性的所在型別繼承自DependencyObject類。DependencyObject 類提供了GetValue、 SetValue方法
- 使用public static 宣告一個DependencyProperty的變數,該變數就是真正的依賴屬性。
- 提供一個依賴屬性的包裝屬性,通過這個屬性來完成對依賴屬性的讀寫操作。
根據上面的四個步驟,下面來把Name屬性來改寫成一個依賴屬性,具體的實現程式碼如下所示:
1. 使型別繼承DependencyObject類
public class Person : DependencyObject
{
// 2. 宣告一個靜態只讀的DependencyProperty 欄位 // 3. 註冊定義的依賴屬性
public static readonly DependencyProperty NameProperty=DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("Learning Hard",OnValueChanged));
// 4. 屬性包裝器,通過它來讀取和設定我們剛才註冊的依賴屬性 public void SetValue (DependencyProperty dp, object value);
public string Name
{
get { return (string)GetValue(nameProperty); }
set { SetValue(nameProperty, value); }
}
private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
{
// 當只發生改變時回撥的方法
}
}
第四個引數支援 PropertyMetadate 型別,同時它派生的UIPropertyMetadata型別
以及UIPropertyMetadata派生的FrameworkPropertyMetadata 在不同型別上實現的效果略有差異,此次提出一個典型
若依賴屬性為ObservableCollection<T>的時候,必須要使用FrameworkPropertyMetadata作為引數,程式碼如下,給DefaultVaule、DefaultUpdateSourceTrigger 賦預設值。
new FrameworkPropertyMetadata { DefaultValue = new ObservableCollection<T>(), DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
自定義控制元件實現依賴屬性有如下三個注意點
- 定義物件是int、string等型別的時候,可以使用PropertyMetadate作為觸發引數
- 若定義的依賴屬性為ObservableCollection 則register 中的第四個引數應該調整為 FrameworkPropertyMetadata,並附上預設值。
- XAML繫結 依賴屬性,必須通過 RealtiveSource 實現繫結,不同能通DataContext Self 的形式,因為UserContrl 需要對後續資料來源的輸入,若使用DataContext Self 的形式,則無法實現資料更新