Silverlight基礎(chǔ)屬性:依賴與附加
作為微軟進(jìn)軍RIA領(lǐng)域的武器,Silverlight正在得到微軟的大力推廣,其***版本也在不久前的MIX10大會上發(fā)布,新版本不僅增多了50多項(xiàng)功能,同時也向我們呈現(xiàn)了各種開發(fā)工具包。
在Silverlight中,依賴屬性(Dependency Property)和附加屬性(Attached Property)這兩個算是很基礎(chǔ)的知識,下面我們著重分析這兩個屬性。
CLR屬性與依賴屬性
CLR屬性我們非常熟悉了,在DotNet編程中隨處可見。最簡單最常見的屬性訪問器就是直接操縱類的私有成員,如下:
- public class Person{
 - private String _name;
 - public string Name
 - {
 - get { return _name; }
 - set { _name = value; }
 - }}
 
C#3.0對這種常見的寫法提供了“自動屬性”這一特性,方便了偶等這些懶惰的碼農(nóng)。
- public class Person{
 - public string Name { get; set; }
 - }
 
這兩種寫法是等價的,都是需要設(shè)立一個實(shí)例級的私有變量作為屬性訪問器的持久存儲。這對于我們非UI應(yīng)用來說沒什么。因?yàn)?**,我們一般不會創(chuàng)建太多類實(shí)例;第二,一個類的屬性通常不會很多,加幾個私有變量不會增加系統(tǒng)負(fù)擔(dān)。但是這兩個理由對于UI應(yīng)用程序來說恰恰不成立。
在很多UI應(yīng)用中,我們經(jīng)常會創(chuàng)建很多類實(shí)例,成千上萬個實(shí)例在UI系統(tǒng)中是很普遍的事情。同時,UI類通常會包含大量的屬性供設(shè)計(jì)人員使用,例如背景顏色,前景顏色,字體,邊距等等,這些屬性在絕大多數(shù)情況下會保持默認(rèn)值,如果為每個實(shí)例都建立這么多的私有變量來存儲UI屬性的值,勢必會造成極大的浪費(fèi),對系統(tǒng)負(fù)擔(dān)的開銷也是不小。鑒于以上提到的問題,設(shè)計(jì)一個高效的屬性存儲系統(tǒng)對于UI應(yīng)用程序的開發(fā)是非常重要的。因此Silverlight引入了“依賴屬性(DependencyProperty)”。
采用鍵值對替代成員變量作為屬性內(nèi)部存儲
傳統(tǒng)CLR屬性,一個屬性對應(yīng)一個私有變量,UI元素的屬性那么多,創(chuàng)建過多的私有變量不是一件簡單的事情,況且大多數(shù)屬性只會用到默認(rèn)值。因此Silverlight使用鍵值對的形式來存放那些用戶顯式設(shè)置的屬性(稱為Local Value本地值),沒有設(shè)置的屬性就不存。那屬性的默認(rèn)值存放在哪?既然各個實(shí)例的默認(rèn)值都一樣(不然也不叫默認(rèn)值了),那么直接存放到靜態(tài)成員變量上就行了。這也就大大提高了存儲的效率。
注冊依賴屬性
既然依賴屬性采用鍵值對這樣的哈希結(jié)構(gòu)進(jìn)行存儲,那么要獲取不同屬性的值,我們就必須使用不同的哈希鍵,否則就會讀取到其他屬性的值了。因此,當(dāng)我們在向Silverlight屬性系統(tǒng)注冊依賴屬性的時候,Silverlight會返回一個唯一的屬性標(biāo)識對象,類型為DependencyProperty。我們以后就通過這個唯一標(biāo)識對象去訪問依賴屬性的值。
由于這個唯一標(biāo)識符是所有類實(shí)例都公用并且不會被修改的,因此我們通常將其保存到一個static readonly的成員變量中。DependencyProperty類提供了兩個方法,一個是Register方法,用于注冊依賴屬性;另外一個是RegisterAttached,用于注冊附加屬性。
- public static DependencyProperty Register(string name,Type propertyType,Type ownerType,PropertyMetadata typeMetadata)
 
Register方法的簽名由幾部分組成,Name參數(shù)指明了依賴屬性使用的名稱,這個名字很重要,在定義控件Style和Template的時候,Setter的Property屬性填入的值就是注冊依賴屬性時使用的名稱;propertyType指明了依賴屬性實(shí)際的類型,ownerType指明了是哪個類注冊了此依賴屬性,***typeMetadata存放了一些依賴屬性的元信息,包括依賴屬性使用的默認(rèn)值,還有屬性值發(fā)生變更時的通知函數(shù)。 #p#
屬性的存取
和CLR屬性不同,依賴屬性不是直接對私有變量的操縱,而是通過GetValue和SetValue的方法來操作屬性值的。下面的代碼演示了為Ball控件設(shè)置一個Center的依賴屬性,并且在程序中讀取和修改此屬性的過程:
- public class Ball : Control{
 - public static readonly DependencyProperty CenterProperty =
 - DependencyProperty.Register("Center", typeof(Point), typeof(Ball), null);
 - }
 - public class BallApp{ public void RollBall(Ball ball)
 - {
 - Point curCenter = (Point)ball.GetValue(Ball.CenterProperty);
 - curCenter.X++; // 注意對值類型對象操作完畢之后一定要調(diào)用SetValue修改才能生效
 - ball.SetValue(Ball.CenterProperty, curCenter);
 - }}
 
由于上述對依賴屬性的操作經(jīng)常需要涉及到類型的轉(zhuǎn)換,比較麻煩,而傳統(tǒng)CLR屬性用起來和直接操縱普通變量一樣方便,因此通常在設(shè)計(jì)依賴屬性的時候,都會使用CLR屬性將其包裝起來,我們稱之為增強(qiáng)型的CLR屬性。
- public class Ball : Control{
 - public static readonly DependencyProperty CenterProperty =
 - DependencyProperty.Register("Center", typeof(Point), typeof(Ball), null);
 - public Point Center
 - { get { return (Point)GetValue(CenterProperty); }
 - set { SetValue(CenterProperty, value); } }}
 
按照約定,依賴屬性的名稱通常是相應(yīng)CLR屬性名稱后面加上個“Property”字符串。事實(shí)上,使用CLR包裝依賴屬性并不只是為了方便,很多依賴于CLR屬性作為基礎(chǔ)的工具或者子系統(tǒng)并不能直接訪問依賴屬性,而只能通過CLR屬性去間接訪問依賴屬性。
例如上面的例子中,假設(shè)我們并沒有設(shè)置一個Center的CLR屬性,那么以下的Xaml將會編譯失敗,因?yàn)閄aml解析器無法知道Ball類有一個Center的依賴屬性(在Style中設(shè)置Center屬性值就可以編譯成功,因?yàn)镾tyle是動態(tài)查找屬性的)。<Ball Center="2" />依賴屬性的尋值邏輯和值變更通知,上面提到的只是依賴屬性相比CLR屬性在存儲效率的不同,實(shí)際上,依賴屬性還有其他實(shí)用的特性。
尋值邏輯
CLR屬性在獲取值的時候是直接讀取成員變量值返回的,而依賴屬性在使用的時候是通過GetValue函數(shù)的調(diào)用來獲取屬性的值。實(shí)際上,GetValue內(nèi)部做的事情可不止是簡單的讀取字典里頭存放的值。他還有尋值邏輯。如下圖所示:
當(dāng)你調(diào)用GetValue去讀取一個依賴屬性的值的時候,Silverlight的屬性系統(tǒng)會首先從動畫系統(tǒng)中查找當(dāng)前是否有作用在此依賴屬性上的動畫,如果有,則返回此動畫值。從這里也可以看出,依賴屬性是Silverlight實(shí)現(xiàn)動畫機(jī)制的基礎(chǔ)。注意,如果動畫已經(jīng)停止了,并且沒有設(shè)置FillBehavior=HoldEnd的話,那么Silverlight就不會返回此動畫值。
如果讀不到動畫值,那么Silverlight就會嘗試讀取本地值。本地值有幾種類型,一種是用戶通過代碼或者Xaml直接設(shè)定的值。一種是通過資源綁定得到的值,***一種是通過數(shù)據(jù)綁定得到的值。這些都被視為本地值。
- <StackPanel x:Name="LayoutRoot">
 - <StackPanel.Resources>
 - <System:String x:Key="TextBlockResource">資源數(shù)據(jù)綁定文本
 - </System:String> </StackPanel.Resources>
 - <TextBlock Text="{Binding Source={StaticResource TextBlockResource}}" />
 - <TextBlock x:Name="DataBindingElement" Text="{Binding ElementName}" />
 - </StackPanel>
 
如果還是讀取不到,那么就繼續(xù)嘗試讀取控件模板和樣式中設(shè)置的值。如果所有這些值都讀取失敗,那么Silverlight屬性系統(tǒng)就會返回該依賴屬性的默認(rèn)值。當(dāng)我們注冊依賴屬性的時候,可以傳入一個PropertyMetaData對象,這個對象包含了此依賴屬性的默認(rèn)值和值變更通知回調(diào)函數(shù)。如果注冊的時候沒有傳入默認(rèn)值,則對于引用類型的依賴屬性,返回null,對于字符串,返回String.Empty,對于值類型,則返回一個以默認(rèn)值初始化的實(shí)例。這里需要對集合類型特別注意,由于通過PropertyMetaData傳入的默認(rèn)值是所有類實(shí)例共享的,因此,一定要在類構(gòu)造函數(shù)中顯式傳入集合的實(shí)例。
- public class GameRoom : Control{
 - public List<Ball> Balls
 - {
 - get { return (List<Ball>)GetValue(BallsProperty);
 - }
 - set { SetValue(BallsProperty, value); }
 - }
 - public static readonly DependencyProperty BallsProperty =
 - DependencyProperty.Register("Balls", typeof(List<Ball>), typeof(GameRoom), null);
 - public GameRoom() {
 - Balls = new List<Ball>(); }}
 
可能正是因?yàn)镾ilverlight的依賴屬性在獲取值的時候需要從多個地方去讀取值,而不是像CLR屬性一樣,直接從成員變量中讀取值,所以才被稱之為“依賴”屬性吧。#p#
值變更通知
屬性值的變更通知我們并不陌生。我們在DotNet中實(shí)現(xiàn)的時候,一般是讓類實(shí)現(xiàn)INotifyPropertyChanged接口。在UI系統(tǒng)中,值變更通知是經(jīng)常需要用到的。數(shù)據(jù)源一旦變更,所有相應(yīng)的UI元素的值都要相應(yīng)的做出調(diào)整。Silverlight的依賴屬性對此有內(nèi)置的支持。只要你在綁定時使用依賴屬性,那么當(dāng)依賴屬性值發(fā)生變更的時候,所有綁定的地方的值都會同步更新。而且,依賴屬性也提供了一個值變更通知函數(shù)(在注冊依賴屬性時通過PropertyMetaData傳入),你可以自定義一個函數(shù)來控制值變更時需要執(zhí)行的操作。
- public class Ball : Control{
 - public static readonly DependencyProperty
 - CenterProperty=DependencyProperty.Register("Center", typeof(Point), typeof(Ball), new PropertyMetadata(OnCenterChanged));
 - public Point Center {
 - get { return (Point)GetValue(CenterProperty);
 - }
 - set { SetValue(CenterProperty, value); }
 - }
 - private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 - {
 - Ball ball = d as Ball; // 獲取新的球心
 - Point newCenter = (Point)e.NewValue; // Silverlight的附加屬性(Attached Property)
 
全局的依賴屬性
剛才提到的依賴屬性和CLR屬性一樣都是服務(wù)于某一個類的。只不過將屬性改造得存儲上更有效率,使用上更加強(qiáng)大。在Silverlight中還有一種特殊的依賴屬性,這種依賴屬性并不只是服務(wù)于某個特定的類,而是服務(wù)于全局,這就是附加屬性。從名字上也可以看出來,附加屬性是在某個類里面注冊,然后可以被其他類所使用。什么情況下需要使用附加屬性呢?舉Canvas類的ZIndex屬性作為例子。容器類在疊加子控件的時候,需要考慮哪個控件放置在最上面,那個放在下面。
那么容器類怎么知道子控件的疊放順序呢?最不動腦子的設(shè)計(jì)就是為所有的控件都添加一個ZIndex的屬性,屬性的值代表疊放的順序。但這樣的后果就是,如果我這個控件不參與布局,那多這個屬性就會顯得很浪費(fèi)。所以比較理想的設(shè)計(jì)是,需要用到這個屬性的時候就有這個屬性,不需要的時候就沒有這個屬性的負(fù)擔(dān)。附加屬性的出現(xiàn)就是為了解決這樣的問題。一旦控件需要某個屬性的時候,我們可以把這個屬性附加到這個控件類上。注冊附加屬性和依賴屬性差不多,只不過函數(shù)名為RegisterAttached。這個就不多說了。
什么時候應(yīng)該用到依賴屬性
既然依賴屬性那么高效,而且那么強(qiáng)大,那么我們是不是應(yīng)該保持使用依賴屬性的習(xí)慣呢?事實(shí)上,任何好處都是有代價的。Silverlight的依賴屬性在訪問效率上并不如直接訪問成員變量那么高效。因此,對于那些比較簡單而訪問頻率又非常高的屬性,建議還是使用傳統(tǒng)的CLR屬性去實(shí)現(xiàn)。
【編輯推薦】
- 簡單Silverlight應(yīng)用程序五步走
 - Silverlight WCF服務(wù)正確組建方法淺談
 - Silverlight調(diào)用WCF出現(xiàn)異常解決方案
 - 細(xì)數(shù)2009年Silverlight十大流行應(yīng)用
 - 為你揭開Silverlight代碼安全性秘密
 















 
 
 

 
 
 
 