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