[WPF][Silverlight] 你不能不了解的 DependencyProperty

  • 17227
  • 0
  • C#
  • 2013-07-14

在WPF和Silverlight出現之前,我們常常藉由在class中撰寫Property,來供class外部存取class中的變數;但是在WPF出現了之後,也帶出了DependencyProperty這個全新的玩意,它的運作機制和我們之前所熟悉的CLR Property(或稱.Net Proprety,之後本文中我們統一使用CLR Property這個名詞)可是有極大差異的....



在WPF和Silverlight出現之前,我們常常藉由在class中撰寫Property,來供class外部存取class中的變數;但是在WPF出現了之後,也帶出了DependencyProperty這個全新的玩意,它的運作機制和我們之前所熟悉的CLR Property(或稱.Net Proprety,之後本文中我們統一使用CLR Property這個名詞)可是有極大差異的,如果你對WPF或是Silverlight的程式開發有興趣,或者你正在從事這樣的工作的話,你不能不懂DependencyProperty。
 
讓我們來看看DependencyProperty和一般的CLR Property有什麼不一樣:

從程式的結構面來說,DependencyProperty只能被封裝在繼承DependencyObject的物件中,所以幾乎我們在WPF或是Silverlight中看得到的控制項都繼承了DependencyObject,而且DependencyProperty都是靜態屬性,光這點就和我們習慣的CLR Property差很多了吧!
 
而從功能和使用性來說,CLR Property通常只用來封裝我們不想直接公開的Field,並且透過get/set,來讓外部存取,但是DependencyProperty除了用來封裝資料之外,還有以下特性:
 

  1. Property值的繼承:DependencyProperty的值是有繼承關係的,舉例來說,容器的DataContext也能被容器中的控制項存取到(但是並非所有DependencyProperty都會有繼承的機制在,因為會影響到效能)。
  2. 可以當作Resource使用: DependencyProperty可以簡單的透過XAML標籤被定義在 <Resources></Resources>區段中。 例如:
    
    <Grid>
        <Grid.Resources>
     
            <SolidColorBrush x:Key="MyBrush" Color="Black"/>
     
        </Grid.Resources>
     
        <Button Background="{DynamicResource MyBrush}" Content="I’m Black!" >
    </Grid>
    
  3. 可以做為Style的成員我們可以利用<Setter>這個Tag在Style中更改控制項的某個DependencyProperty值。 例如:

    
    <Style x:Key="BlackButtonStyle">
     
        <Setter Property="Control.Background" Value="Black"/>
     
    </Style>
     
    <Button Style="{StaticResource BlackButtonStyle}">I’m Black!</Button>
    
  4. 可以做為Template的成員和Style類似,我們也能在Template中操作DependencyProperty。
  5. 可以做為動畫的成員之一例如常見的DoubleAnimation就是去改變一個型別為double的DependencyProperty的值,達到動畫的效果。
  6. 資料驗證:我們可以在當DependencyProperty的值改變的時候,讓它觸發事件,進行資料的驗證。
  7. DataBinding 之前在介紹DataBinding的時候有介紹到要做到DataBinding必需要控制項中有開放出DependencyProprety搭配INotifyPropertyChanged來進行binding。
  8. CallBacks :我們可以透過DependencyProperty的機制,輕易的做到當DependencyProperty的值改變的時候,就觸發事件,來做特定的工作。
  9. Metadata overrides : 可以透過OverrideMetadata方法來改變原來定義好的行為,或是DependencyProprety的預設值。
  10. 對使用者介面設計師的親和支援: 如果DependencyProperty的型別是基礎型別的話,在Expression Blend中會自動的出現在Property視窗中供設計師直接修改裡面的值,而且如果有定義ValueChanged Callback的話,也會在設計介面中反映出來(這個在Silverlight 3.0版之前是一定得這樣做才行的,但是到4.0版之後,CLR Property也會出現在Property視窗中)。
  11. 節省資源、提升效能:前面講過,DependencyProperty都是靜態屬性,假如有一個class中擁有100個CLR Property,那每產生一個該class的實體,記憶體就得分配位置給那100個Property使用,但是DependencyProperty則不然,當該DependencyProperty沒被使用到的時候,是不會佔用到記憶體空間的。
  12. 不改變使用習慣:DependencyProperty在使用方面上,跟之前的CLR Property幾乎是一模一樣的,都是透過get/set來控制存取,不過在定義時就有比較大一點的差異。

講了一堆,再來講講怎麼實作DependencyProperty吧!!
通常DependencyProperty最基礎的結構如下(以Silverlight為例):


public static readonly DependencyProperty MyNumberProperty = DependencyProperty.Register( "MyNumber" , typeof( int ) , typeof( MyUserControl ) , null );
 
public int MyNumber
{
 
    get
    {
 
        return ( int ) this.GetValue( MyNumberProperty );
 
    }
 
    set
    {
 
        this.SetValue( MyNumberProperty , value );
 
    }
 
}

   
從上例中我們就可以看出DependencyProperty和傳統的CLR Property在實作上的差異了吧!
首先,DependencyProperty必需是一個public static readonly的DependencyProperty型別物件,而且是透過DependencyProperty.Register()這個方法來建立。
Register這個方法需傳入四個參數,
第一個是DependencyProperty所對應到的CLR Property名稱(我們在下面也定義了一個CLR Property,請注意,是名稱,所以該值為字串)。
第二個參數是該DependencyProperty值的型別(必需跟對應的CLR Property型別一致)。
第三個是該DependencyProperty要被註冊的宿主的型別(該型別必需繼承DependencyObject)。
最後則是DependencyProperty的MetaData(在Silverlight中,若不需要MetaData,可將該參數設為null;而在WPF中則是有多載的方法,可以直接省略該參數)。

而下面的CLR Property也有不一樣的地方,得透過DependencyObject的GetValueSetValue兩個Method來存取DependencyProperty的值。

到這邊,應該就又有人要舉手發問了:「說好的其他優點呢?現在看起來只是寫起來更麻煩而已啊!!說好的預設值和Callback那些呢??」別急~別急!!接下來才是關鍵(建議大家先讓自己清醒一點再看下去),預設值和Callback都可以透過PropertyMetadata設定,那PropertyMetadata該怎麼用呢?PropertyMetadata有多重建構子,大家可以參考MSDN:WPF的PropertyMetadataDependency Property Callbacks and ValidationSilverlight的PropertyMetadata (使用WPF的人還是比較幸福啊,還能在PropertyMetadata裡面做到資料的驗證,Silverlight的話就得多花一點工了)。
所以綜合一下,如果我們想要替DependencyProperty加上預設值,並且當值改變的時候會觸發事件的話,我們可以把上面的程式改成這樣:


public static readonly DependencyProperty MyNumberProperty = DependencyProperty.Register( "MyNumber" , typeof( int ) , typeof( MyUserControl ) , 
    new PropertyMetadata( 3 , MyNumberPropertyChanged ) );
 
public int MyNumber
{
    get
    {
        return ( int ) this.GetValue( MyNumberProperty );
    }
    set
    {
        this.SetValue( MyNumberProperty , value );
    }
}
 
private static void MyNumberPropertyChanged( DependencyObject obj , DependencyPropertyChangedEventArgs args )
{
    if( obj != null )
    {
        MyUserControl myUserControl = obj as MyUserControl;
 
        //透過myUserControl與使用者介面做互動
    }
}

比較特別的地方是:因為DependencyProperty是static的,所以跟他對應的Callback方法也必需是static的

最後再補充一個重點,DependencyProperty不只是要用來讓外部存取值的,更重要的是要利用ChangeCallback來和宿主互動,除了修改宿主其他的Property之外,也可以呼叫Storyboard,或是其他的Method,好好運用的話可以做到事半功倍的效果喔!!如果找不到好的DependencyProperty範例的話,不妨參考Silverlight Toolkit中的DataPager.cs,相信能讓你更有Fu喔!!

這篇寫得有點長了,我想就在這邊結束,順便送給大家兩個實用的工具吧:
Silverlight DependencyProperty程式碼產生器
WPF DependencyProperty程式碼產生器

要好好利用DependencyProperty喔!!如果有希望更深入了解的朋友再麻煩留言或是利用MSN敲我吧!!