目錄
- 屬性
- 依賴屬性(Dependency Property)
- 依賴屬性對記憶體的使用方式
- 宣告和使用依賴屬性
- 宣告依賴屬性
- 使用依賴屬性
- 依賴屬性的“屬性”
- 依賴屬性的“依賴”性
- 添加CRL屬性外包裝:
- 使用依賴屬性形成Binding鏈
- 依賴屬性的DefaultMetadata屬性
- 依賴屬性值存取的秘密
- DependencyProperty.Register方法
- DependencyObject.GetValue方法
- DependencyObject.SetValue方法
- 附加屬性(Attached Properties)
- 附加屬性的含義
- 宣告、注冊和使用
- 自定義類之間使用附加屬性
- 如何在XAML和C#代碼中直接為附加屬性賦值
- 使用Binding依賴在其他物件的資料上
屬性
.NET Framework中的屬性又稱為CLR屬性(CLR,Common Language Runtime),既可以說CLR屬性是private欄位的安全訪問包裝(Get/Set方法),也可以說一個private欄位在后臺支持(back)一個CLR屬性,
C#代碼中的屬性的編譯結果是兩個方法,再多實體方法也只有一個拷貝,CLR屬性并不會增加記憶體的負擔,屬性僅僅是個語法糖衣(Syntax Sugar),
相關資料參考:屬性(C# 編程指南)
依賴屬性(Dependency Property)
在WPF中,微軟推出了“依賴屬性”這個新概念,依賴屬性就是一種可以自己沒有值,并能通過使用Binding從資料源獲得值(依賴在別人身上)的屬性,擁有依賴屬性的物件被稱為“依賴物件”,
與傳統的CLR屬性和面向物件思想相比依賴屬性有很多新穎之處,其中包括:
- 節省實體對記憶體的開銷,
- 屬性值可以通過Binding依賴在其他物件上,
依賴屬性對記憶體的使用方式
傳統的.NET開發中,一個物件所占用的記憶體空間在呼叫new運算子進行實體化的時候就已經決定了,而WPF允許物件在被創建的時候并不包含用于存盤資料的空間(即欄位所占用的空間)、只保留在需要用到資料時能夠獲得默認值、借用其他物件資料或實時分配空間的能力一一這種物件就稱為依賴物件(Dependency Object)而它這種實時獲取資料的能力則依靠依賴屬性(Dependency Property)來實作,
在WPF開發中,必須使用依賴物件作為依賴屬性的宿主,使二者結合起來,才能形成完整的Binding目標被資料所驅動,
在WPF系統中,依賴物件的概念被DependencyObject類所實作,依賴屬性的概念則由DependencyProperty類所實作,
DependencyObject具有GetValue和SetValue兩個方法:
public class DependencyObject : DispatcherObject
{
public void ClearValue(DependencyProperty dp)
{
//...
}
public void SetValue(DependencyProperty dp, object value);
{
//...
}
}
這兩個方法都以DependencyProperty物件為引數,GetValue方法通過DependencyProperty物件獲取資料;SetValue通過DependencyProperty物件存盤值——正是這兩個方法把DependencyObject和DependencyProperty緊密結合在一起,
DependencyObject是WPF系統中相當底層的一個基類,繼承樹如下所示:

WPF的所有UI控制元件都是依賴物件,WPF的類別庫在設計時充分利用了依賴屬性的優勢,UI控制元件的絕大多數屬性都已經依賴化了,
宣告和使用依賴屬性
準備好一個界面,如下所示:
<StackPanel>
<TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
<Button Content="OK" Margin="5" Click="Button_Click"/>
</StackPanel>

宣告依賴屬性
DependencyProperty必須以DependencyObject為宿主、借助它的SetValue和GetValue 方法進行寫入與讀取,因此,**想使用自定義的DependencyProperty,宿主一定是DependencyObject 的派生類,
DependencyProperty實體的宣告特點很鮮明——參考變數由public static readonly三個修飾符修飾,實體并非使用new運算子得到而是使用DependencyProperty.Register方法生成,代碼如下:
public class Student : DependencyObject
{
public static readonly DependencyProperty NameProperty=DependencyProperty.Register("Name", typeof(string), typeof(Student));
}
命名約定:成員變數的名字需要加上Property后綴以表明它是一個依賴屬性,
上面使用的是DependencyProperty.Register方法引數最少、最簡單的一個多載,分析一下這3個引數:
- 第1個引數為string型別,用這個引數來指明以哪個CLR屬性作為這個依賴屬性的包裝器,或者說此依賴屬性支持(back)的是哪個CLR屬性,將來會使用名為Name的CLR屬性來包裝它,所以這個引數被賦值為Name,
- 第2個引數用來指明此依賴屬性用來存盤什么型別的值,學生的姓名是string型別,所以這個引數被賦值為typeof(string),
- 第3個引數用來指明此依賴屬性的宿主是什么型別,或者說DependencyProperty.Register方法將把這個依賴屬性注冊關聯到哪個型別上,本例中的意圖是為Student類準備一個可依賴的名稱屬性,所以這個引數被賦值為typeof(Student),
這里有三點需要注意:
- 依賴屬性的包裝器(Wrapper)是一個CLR屬性,實際上的依賴屬性是那個由public static readonly修體的Dependency Property實體,有沒有包裝器這個依賴屬性都存在,
- 包裝器的作用是以“實體屬性”的形式向外界暴露依賴屬性,這樣一個依賴屬性才能成為資料源的一個Path,
- 注冊依賴屬性時使用的第二個引數是一個資料型別,這個資料型別也是包裝器的資料型別,它的全稱應該是“依賴屬性的注冊型別”,一般情況下也會把這個型別型別稱為“依賴屬性的型別”,
使用依賴屬性
理解了依賴屬性宣告變數和創建實體的程序,就可以嘗試使用它了,
依賴屬性的“屬性”
依賴屬性首先是屬性,嘗試用依賴屬性來存盤值并把值順利讀取出來,
UI中OK按鈕的Click事件處理器代碼如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
//創建一個Student 實體并使用變數stu參考
Student stu = new Student();
//呼叫SetValue方法把textBox1.Text 屬性的值存盤進依賴屬性
stu.SetValue(Student.NameProperty, this. textBox1.Text);
//使用GetValue方法把值讀取出來,注意SetValue的回傳值是object型別,要進行適當的型別轉換
textBox2.Text = (string)stu.GetValue(Student.NameProperty);
}
Student類的SetValue和GetValue方法繼承自DependencyObject類,效果如下所示:

依賴屬性的“依賴”性
讓textBox1作為資料來源,把Student實體作為資料的目標,讓Student實體依賴在textBoxl上(僅僅是為了展示依賴屬性的“依賴”功能,現實作業中幾乎從來不這么做),代碼如下:
Student stu;
public MainWindow()
{
InitializeComponent();
stu = new Student();
//創建一個Binding的實體,讓textBox1作為資料源物件并從其Text屬性中獲取資料
Binding binding = new Binding("Text") { Source = textBox1 };
//使用BindingOperations類的SetBinding方法指定將stu物件借助剛剛宣告的Binding實體依賴在textBox1上
BindingOperations.SetBinding(stu, Student.NameProperty, binding);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//彈出對話框顯示依賴屬性的值
MessageBox.Show(stu.GetValue(Student.NameProperty).ToString());
}
注:依賴屬性即使沒有CLR屬性作為其外包裝也可以很好地作業,
如果想把textBox1和textBox2關聯起來,代碼如下:
Binding binding = new Binding("Text") { Source = textBox1 };
textBox2.SetBinding(TextBox.TextProperty, binding);
DependencyObject類(Student類的基類)沒有SetBinding方法,SetBinding 方法是FrameworkElement類的方法,FrameworkElement是個相當高層的類(比UIElement類還高),這從側面表明微軟希望能夠SetBinding(即作為資料目標)的物件是UI元素,
FrameworkElement類的SetBinding方法僅僅對BindingOperations的SetBinding方法做了一個簡單的封裝,代碼如下:
public class FrameworkElement : UIElement //…
{
//…
public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
{
return BindingOperations.SetBinding(this, dp, binding);
}
//…
}
添加CRL屬性外包裝:
現在使用的依賴屬性依靠SetValue和GetValue兩個方法進行對外界的暴露,而且在使用GetValue的時候還需要進行一次資料型別的轉換,所以大多數情況下會為依賴屬性添加一個CRL屬性外包裝,代碼如下:
public class Student : DependencyObject
{
//CLR屬性包裝器
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty=DependencyProperty.Register("Name", typeof(string), typeof(Student));
}
通過CLR屬性包裝訪問依賴屬性,代碼如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
Student stu = new Student();
stu.Name = this.textBox1.Text;
this.textBox2.Text = stu.Name;
}
依賴物件可以通過Binding依賴在其他物件上,即依賴物件是作為資料的目標而存在的,為依賴物件的依賴屬性添加CLR屬性包裝就相當于為依賴物件準備了用于暴露資料的Binding Path,現在的依賴物件已經具備了扮演資料源和資料目標雙重角色的能力,盡管Student類沒有實作INotifyPropertyChanged介面,當屬性的值發生改變時與之關聯的Binding物件依然可以得到通知,依賴屬性默認帶有這樣的功能,天生就是合格的資料源,
使用依賴屬性形成Binding鏈
向FrameworkElement類借用一下它的SetBinding方法、升級一下Student類,代碼如下:
public class Student : DependencyObject
{
//CLR屬性包裝器
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
//依賴屬性
public static readonly DependencyProperty NameProperty=DependencyProperty.Register("Name", typeof(string), typeof(Student));
//SetBinding 包裝
public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
{
return BindingOperations.SetBinding(this,dp,binding);
}
}
使用Binidng把Student物件關聯到textBox1上,再把textBox2關聯到Student物件上形成Binding鏈,代碼如下:
Student stu;
public MainWindow()
{
InitializeComponent();
stu = new Student();
stu.SetBinding(Student.NameProperty, new Binding("Text") { Source = textBox1 });
textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//便于監視依賴屬性的值
MessageBox.Show(stu.GetValue(Student.NameProperty).ToString());
}
運行程式,當在textBoxl中輸入字符的時候,textBox2就會同步顯示,此時Student物件的Name屬性值也同步變化了,
注:在一個類中宣告依賴屬性時并不需要手動進行宣告、注冊并使用CLR屬性封裝,只需要輸入propdp連按兩次Tab鍵,一個標準的依賴屬性(帶CLR屬性包裝)就宣告好了,繼續按動Tab鍵,可以在提示環境中修改依賴屬性的各個引數,
依賴屬性的DefaultMetadata屬性
在自動生成的代碼中,DependencyProperty.Register使用的是帶4個引數的多載,前3個引數與前面介紹的一致,第4個引數的型別是PropertyMetadata類,
第4個引數的作用是給依賴屬性的DefaultMetadata屬性賦值,DefaultMetadata的作用是向依賴屬性的呼叫者提供一些基本資訊,資訊包括:
- CoerceValueCallback:依賴屬性值被強制改變時此委托會被呼叫,此委托可關聯一個影響函式,
- DefaultValue:依賴屬性未被顯式賦值時,若讀取之則獲得此默認值,不設此值會拋出例外,
- IsSealed:控制PropertyMetadata的屬性值是否可以更改,默認值為true,
- PropertyChangedCallback:依賴屬性的值被改變之后此委托會被呼叫,此委托可關聯一個影響函式,
依賴屬性的DefaultMetadata只能通過Register方法的第4個引數進行賦值,而且一旦賦值就不能改變(DefaultMetadata是個只讀屬性),如果想用新的PropertyMetadata 替換這個默認的Metadata,需要使用DependencyProperty.OverrideMetadata方法,
依賴屬性值存取的秘密
依賴物件的依賴屬性是一個static物件,呼叫依賴物件的SetValue方法時值不可能是保存在static物件里,重點分析DependencyProperty.Register方法和DependencyObject.SetValue方法和DependencyObject.GetValue方法,
DependencyProperty.Register方法
DependencyProperty.Register方法由名稱可知,不僅要創建DependencyProperty實體,還要對它進行“注冊”,
閱讀原始碼會發現DependencyProperty類具有這樣一個成員:
private static Hashtable PropertyFromName = new Hashtable();
一旦程式運行,就會有這樣一個全域的Hashtable存在,這個Hashtable就是用來注冊DependencyProperty實體的地方,
在原始碼中,所有的DependencyProperty.Register方法多載最后都歸結為對DependencyProperty.RegisterCommon方法的呼叫(可以把RegisterCommon理解為Register方法的“完整版”),RegisterCommon方法的原始碼如下:
/*
* RegisterCommon方法的前4個引數與前面分析過的Register方法一致
*/
private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
/*
* FromNameKey 是一個,NET Framework內部資料型別,它的構造器代碼如下:
* public FromNameKey(string name,Type ownerType)
* {
* _name = name;
* _ownerType = ownerType;
* hashCode = _name.GetHashCode()^_ownerType.GetHashCode();
* }
*
* 并且override有其GetHashCode方法:
* public override int GetHashCode()
* {
* return _hashCode;
* }
*
*
* 由上面的代碼可知:FromNamekey物件(也就是變數key)的hash code實際上是RegisterCommon第1個引數(CLR屬性名字串)的hash code與第3個引數(宿主型別)的hash code做異或運算得來的,
*
*/
FromNameKey key = new FromNameKey(name, ownerType);
lock (Synchronized)
{
/*
* 每對“CLR屬性名一宿主型別”所決定的DependencyProperty實體是唯一的,
* 如果嘗試使用同一個CLR屬性名字和同一個宿主型別進行注冊,程式會拋出例外,
*/
if (PropertyFromName.Contains(key))
{
throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered, name, ownerType.Name));
}
}
/*
* RegisterCommon 檢查程式員是否提供了PropertyMetadate,如果沒有提供則為之準備一個默認的PropertyMetadate實體,
*/
// 為所有型別建立默認metadata(如果未提供)
if (defaultMetadata =https://www.cnblogs.com/timefiles/archive/2021/02/22/= null)
{
defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);
}
else // Metadata物件已經提供
{
// 如果未指定defaultValue,則自動生成一個
if (!defaultMetadata.DefaultValueWasSet())
{
defaultMetadata.DefaultValue = AutoGenerateDefaultValue(propertyType);
}
ValidateMetadataDefaultValue(defaultMetadata, propertyType, name, validateValueCallback);
}
// 創建DependencyProperty的實體
DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
// Seal (null means being used for default metadata, calls OnApply)
defaultMetadata.Seal(dp, null);
if (defaultMetadata.IsInherited)
{
dp._packedData |= Flags.IsPotentiallyInherited;
}
if (defaultMetadata.UsingDefaultValueFactory)
{
dp._packedData |= Flags.IsPotentiallyUsingDefaultValueFactory;
}
// Map owner type to this property
// Build key
lock (Synchronized)
{
//DependencyProperty實體被注冊進Hashtable中(Hashtable會自動呼叫key的GetHashcode 方法獲取其hash code):
PropertyFromName[key] = dp;
}
if (TraceDependencyProperty.IsEnabled)
{
TraceDependencyProperty.TraceActivityItem(
TraceDependencyProperty.Register,
dp,
dp.OwnerType);
}
//生成的DependencyProperty實體被當作回傳值交還
return dp;
}
用一句話概括DependencyProperty物件的創建與注冊,那就是:創建一個DependencyProperty 實體并用它的CLR屬性名和宿主型別名生成hash code,最后把hash code和DependencyProperty 實體作為Key-Value對存入全域的、名為PropertyFromName的Hashtable中,這樣,WFP屬性系統通過CLR屬性名和宿主型別名就可以從這個全域的Hashtable中檢索出對應的DependencyProperty實體,
把DependencyProperty實體注冊進全域Hashtable時使用的key由CLR屬性名哈希值和宿主型別哈希值經過運算得到,但這并不是DependencyProperty實體的哈希值,每個DependencyProperty實體都具有一個名為GilobalIndex的int 型別屬性,GlobalIndex的值是經過一些演算法處理得到的,確保了每個DependencyProperty實體的Giloballndex是唯一的,
DependencyProperty的GetHashCode方法亦被重寫:
public override int GetHashCode()
{
return GlobaIndex;
}
所以,GlobalIndex屬性值也就是DependencyProperty實體的哈希值——這一點非常重要,因為通過這個值就可以直接檢索到某個DependencyProperty實體,
DependencyObject.GetValue方法
GetValue方法原始碼如下:
public object GetValue(DependencyProperty dp)
{
this.VerifyAccess();
if (dp == null)
{
throw new ArgumentNullException("dp");
}
return GetValueEntry(
LookupEntry(dp.GlobalIndex),
dp,
null,
RequestFlags.FullyResolved).Value;
}
方法的前幾行是為了校驗傳入引數的有效性,只有return一句才是核心內容,return陳述句展開可以寫成這樣:
Entrylndex entrylndex = LookupEntry(dp.Globallndex);
EffectiveValueEntry vaueEnry = GetValueEntry(entrylndex, dp,null, RequestFlags.FullyResolved);
return valueEntry.Value;
Entry是“入口”的意思,WPF的依賴屬性系統在存放值的時候會把每個有效值存放在EffectiveValueEntryy類的實體里,每個實體都有自己的入口——檢索演算法只要找到這個入口、走進入口就能拿到依賴屬性的值,
EffectiveValueEntry的所有構造器都包含一個DependencyProperty型別的引數,每個EectiveValueEntry都關聯著一個DependencyProperty,
EffectiveValueEntry類具有一個名為PropertyIndex的屬性,這個屬性的值實際上就是與之關聯的Dependencyroperty的GlobalIndex屬性值,
在DependencyObject類的原始碼中可以找到這樣一個成員變數:
//此DependencyObject的有效值快取
//這是一個使用DP.GlobalIndex排序的陣列,這種排序是通過插入排序演算法來維持的
private EffectiveValueEntry[] _effectiveValues;
這個陣列向我們提示了依賴屬性存盤值的秘密——每個Dependencyobject實體都自帶一個EffectiveValueEntry型別陣列,當某個依賴屬性的值要被讀取時,演算法就會從這個陣列中去檢索值,如果陣列中沒有包含這個值,演算法會回傳依賴屬性的默認值(這個值由依賴屬性的DefaultMetadata來提供),
由上可知,被static關鍵字所修飾的依賴屬性物件其作用是用來檢索真正的屬性值而不是存盤值;被用做檢索鍵值的實際上是依賴屬性的GlobalIndex屬性(本質是其hash code,而 hash code又由其CLR包裝器名和宿主型別名共同決定),為了保證GlobalIndex屬性值的穩定性,宣告的時候又使用了readonly關鍵字進行修飾,
實際作業中,依賴屬性的值除了可能存盤在EffectiveValueEntry陣列或由默認值提供外,還有很多途徑可以獲得,如元素的Style或Theme,WPF對依賴屬性值的讀取優先級由先到后依次是:
(1)WPF屬性系統強制值,
(2)由影片程序控制的值,
(3)本地變數值(存盤在EffectiveValueEntry陣列中),
(4)由上級元素的Template設定的值,
(5)由隱式樣式(Implicit Style)設定的值,
(6)由樣式之觸發器(Style Trigger)設定的值,
(7)由模板之觸發器(Template Trigger)設定的值,
(8)由樣式之設定器(Style Setter)設定的值,
(9)由默認樣式(Default Style)設定的值,默認模式其實就是由主題(Theme)指定的模式,
(10)由上級元素繼承而來的值,
(11)默認值,來源于依賴屬性的元資料(metadata),
DependencyObject.SetValue方法
SetValue方法原始碼如下:
public void SetValue(DependencyProperty dp, object value)
{
// 驗證呼叫執行緒是否有權訪問此物件,只有dispatcher執行緒可以訪問DispatcherObject
this.VerifyAccess();
// 快取此方法無論如何需要獲取的metadata物件
PropertyMetadata metadata = https://www.cnblogs.com/timefiles/archive/2021/02/22/SetupPropertyChange(dp);
// 進行標準屬性設定
SetValueCommon(dp, value, metadata, false /* coerceWithDeferredReference */, false /* coerceWithCurrentValue */, OperationType.Unknown, false /* isInternal */);
}
賦值流程主要有這樣幾個操作:
- 檢查值是不是DependencyProperty.UnsetValue,是則說明呼叫者的意圖是清空現有的值,程式會呼叫ClearValueCommon方法來清空現有的值,
- 檢查EffectiveValueEntry陣列中是否已經存在相應依賴屬性的位置,有則把舊值改寫為新值,沒有則新建EffectiveValueEntry物件并存盤新值,只有被用到的值才會被放進這個串列,WPF系統用演算法(時間)換取了對記憶體(空間)的節省,
- 呼叫UpdateEffectiveValue對新值做一些相應處理,
附加屬性(Attached Properties)
附加屬性的含義
一個屬性本來不屬于某個物件,但由于某種需求而被后來附加上,也就是把物件放入一個特定環境后物件才具有的屬性(表現出來就是被環境賦予的屬性)就稱為附加屬性(Attached Properties),附加屬性的作用是將屬性與資料型別(宿主)解耦,讓資料型別的設計更加靈活,
在Grid里對一個TextBox定位,XAML代碼如下:
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Background="Lime" Grid.Column="1" Grid.Row="1" />
</Grid>
如果TextBox被放置在Canvas里,XAML代碼如下:
<Canvas Margin="10">
<TextBox Background="Lime" Width="200" Canvas.Top="0"/>
<TextBox Background="Lime" Width="200" Canvas.Top="30"/>
<TextBox Background="Lime" Width="200" Canvas.Top="60"/>
</Canvas>
如果TextBox被放在DockPanel里,XAML代碼如下:
<DockPanel LastChildFill="False">
<TextBox Background="Orange" DockPanel.Dock="Top"/>
<TextBox Background="Orange" DockPanel.Dock="Bottom"/>
<TextBox Background="Green" Width="80" DockPanel.Dock="Left"/>
<TextBox Background="Green" Width="80" DockPanel.Dock="Right"/>
</DockPanel>
放在StackPanel里最簡單,XAML代碼如下:
<StackPanel Margin="10,5">
<TextBox Background="LightBlue" Margin="0,5"/>
<TextBox Background="LightBlue" Margin="0,5"/>
<TextBox Background="LightBlue" Margin="0,5"/>
</StackPanel>
TextBox控制元件的設計者不可能知道控制元件發布后程式員是把它放在Grid里還是Canvas里(甚至是以后版本將推出的新布局里),所以也不可能為TextBox準備諸如Column、Row或者Left、Top這類屬性,直接讓布局來決定一個TextBox用什么屬性來設定它的位置,放在Grid里就讓Grid為它附加上Column和Row屬性,放在Canvas 里就讓Canvas為它附加上Top、Left等屬性,放在DockPanel里就讓DockPanel為它附加Dock屬性,
宣告、注冊和使用
附加屬性的本質就是依賴屬性,二者僅在注冊和包裝器上有一點區別,前面用于快速創建依賴屬性的snippet是propdp,這里用于快速創建附加屬性的snippet是propa,
自定義類之間使用附加屬性
以人在學校里會獲得年級和班級兩個屬性為例,人放在學校里會獲得年級和班級兩個屬性說明年級和班級兩個屬性是由學校附加給人的,這兩個屬性的真實所有者(宿主)應該是學校,
準備一個名為School的類,并讓它繼承DependencyObject類,完成附加屬性的框架,代碼如下:
class School : DependencyObject
{
public static int GetGrade(DependencyObject obj)
{
return (int)obj.GetValue(GradeProperty);
}
public static void SetGrade(DependencyObject obj, int value)
{
obj.SetValue(GradeProperty, value);
}
public static readonly DependencyProperty GradeProperty =
DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new UIPropertyMetadata(0));
}
可明顯看出,GradeProperty就是一個DependencyProperty型別成員變數,宣告時一樣使用public static readonly 三個關鍵字共同修飾,唯一的不同就是注冊附加屬性使用的是名為RegisterAttached的方法,但引數卻與使用Register方法無異,
附加屬性的包裝器也與依賴屬性不同——依賴屬性使用CLR屬性對GetValue和SetValue兩個方法進行包裝,附加屬性則使用兩個方法分別進行包裝(為了在使用的時候保持陳述句行文上的通暢),
準備一個派生自DependencyObject、名為Human的類:
class Human:DependencyObject
{
}
在UI上準備一個Button,Click事件的處理器代碼如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
Human human = new Human();
School.SetGrade(human, 6);
int grade = School.GetGrade(human);
MessageBox.Show(grade.ToString());
}
點擊按鈕,效果如下:

這一程序與前面依賴屬性保存值的程序別無二致——值仍然被保存在Human實體的EffectiveValueEntry陣列里,只是用于在陣列里檢索值的依賴屬性(即附加屬性)并不以Human類為宿主而是寄宿在School類里——反正CLR屬性名和宿主型別名只用來生成hash code和Globallndex,
如何在XAML和C#代碼中直接為附加屬性賦值
XAML代碼如下:
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="OK" Grid.Column="1" Grid.Row="1"/>
</Grid>
等效C#代碼如下:
Grid grid = new Grid() { ShowGridLines = true };
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition());
Button button = new Button(){ Content = "OK"};
Grid.SetColumn(button, 1);
Grid.SetRow(button, 1);
grid.Children.Add(button);
this.Content = grid;
使用Binding依賴在其他物件的資料上
附加屬性的本質是依賴屬性一—附加屬性也可以使用Binding依賴在其他物件的資料上,
表單使用Canvas布局,兩個Slider用來控制矩形在Canvas中的橫縱坐標,效果如下:

XAML代碼如下:
<Canvas>
<Slider x:Name="sliderX" Canvas.Top="10" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
<Slider x:Name="sliderY" Canvas.Top="40" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
<Rectangle x:Name="rect" Fill="Blue" Width="30" Height="30" Canvas.Left="{ Binding ElementName=sliderX, Path=Value}" Canvas.Top="{ Binding ElementName=sliderY, Path=Value}"/>
</Canvas>
等效C#代碼(僅Binding部分)如下:
// 設定Binding
this.rect.SetBinding(Canvas.LeftProperty,new Binding("Value") { Source = sliderX });
this.rect.SetBinding(Canvas.TopProperty, new Binding("Value") { Source = sliderY });
由此可見,在使用Binding時除了宿主型別稍有不同外沒有任何區別,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/262332.html
標籤:.NET技术
