1. 程式人生 > 其它 >【WPF】依賴屬性

【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 欄位,作為類的一部分。 此欄位會作為依賴屬性的識別符號。

 在把上面屬性改寫為依賴屬性之前,下面總結下定義依賴屬性的步驟:

  1. 讓依賴屬性的所在型別繼承自DependencyObject類。DependencyObject 類提供了GetValue、 SetValue方法
  2. 使用public static 宣告一個DependencyProperty的變數,該變數就是真正的依賴屬性。
  3. 提供一個依賴屬性的包裝屬性,通過這個屬性來完成對依賴屬性的讀寫操作。

  根據上面的四個步驟,下面來把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)
        {
            // 當只發生改變時回撥的方法
        }

    }

PropertyMetadata:依賴項屬性元資料

第四個引數支援 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 的形式,則無法實現資料更新