DependencyObject和DependencyPorperty兩個類是WPF屬性系統的核心。
在WPF中,依賴對象的概念被DependencyObject類實現;依賴屬性的概念則由DependencyPorperty類實現。
必須使用依賴對象作為依賴屬性的宿主,二者結合起來,才能實現完整的Binding目標被數據所驅動。DependencyObject具有GetValue和SetValue兩個方法,用來獲取/設置依賴屬性的值。
DependencyObject是WPF系統中相當底層的一個基類,如下:
從這顆繼承樹可以看出,WPF的所有UI控件都是依賴對象。WPF的類庫在設計時充分利用了依賴屬性的優勢,UI空間的餓絕大多數屬性都已經依賴化了。
下面用一個簡單的實例來說明依賴屬性的使用方法。先准備好一個界面,順便復習下前面的Style和Template:
<Window x:Class="DependencyObjectProperty.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style x:Key="textStyle" TargetType="{x:Type TextBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <TextBlock Background="CadetBlue" Foreground="HotPink" Text="{TemplateBinding Property=Text}"/> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ScrollViewer SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" x:Name="PART_ContentHost" Background="AliceBlue"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> </Window.Resources> <StackPanel> <TextBox Style="{StaticResource textStyle}" Height="37" Name="textBox1" FontSize="26" Margin="5" Width="439" /> <TextBox Style="{StaticResource textStyle}" Height="37" Name="textBox2" FontSize="26" Margin="5" Width="439" /> <Button Content="Button" Height="39" Name="button1" Width="131" Click="button1_Click" /> </StackPanel> </Window>
前面說過,DependencyProperty必須以DependencyObject為宿主、借助它的SetValue和GetValue方法進行寫入和讀取。因此,想用自定義的DependencyProperty,宿主一定是DependencyObject的派生類。
DependencyProperty實例的聲明特點很明顯:變量由public static readonly三個修飾符修飾,實例使用DependencyProperty.Register方法生成。而非new操作符得到。
代碼如下:
using System.Windows; namespace DependencyObjectProperty { class Student:DependencyObject { //定義依賴屬性 public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student)); } }
這是自定義DependencyProperty的最簡單代碼。
依賴屬性也是屬性,下面來使用它:
private void button1_Click(object sender, RoutedEventArgs e) { Student stu = new Student(); stu.SetValue(Student.NameProperty, textBox1.Text); textBox2.Text = (string)stu.GetValue(Student.NameProperty); }
在textBox1中輸入值,點下Button1后效果如下:
上面我們使用的依賴屬性是靠GetValue和SetValue進行對外的暴露,而且在GetValue的時候需要進行類型的轉換,因此,在大多數的情況下我們會為依賴屬性添加一個CLR屬性的外包裝:
using System.Windows; namespace DependencyObjectProperty { 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屬性包裝,我們就可以和CLR屬性一樣訪問依賴屬性了:
private void button1_Click(object sender, RoutedEventArgs e) { Student stu = new Student(); stu.Name = textBox1.Text; textBox2.Text = stu.Name; }
如果不關心底層的實現,下游的程序員在使用依賴屬性時與使用單純的CLR屬性別無二致。
效果和上面相同:
當然如果不用Binding,依賴屬性的設計就沒有意義,下面我們使用Binding把Student對象關聯到textBox1上,再把textBox2關聯到Student對象上。代碼如下:
private void button1_Click(object sender, RoutedEventArgs e) { Student stu = new Student();
Binding binding = new Binding("Text") { Source = textBox1 }; BindingOperations.SetBinding(stu, Student.NameProperty, binding); Binding binding2 = new Binding("Name") { Source = stu }; BindingOperations.SetBinding(textBox2, TextBox.TextProperty, binding2); }
當然第二個Binding也可以這樣寫,下面兩者等效:
Binding binding2 = new Binding("Name") { Source = stu }; BindingOperations.SetBinding(textBox2, TextBox.TextProperty, binding2);
textBox2.SetBinding(TextBox.TextProperty, binding2);
也可以在Student類中封裝FrameworkElement類的SetBinding方法,如下:
using System.Windows; using System.Windows.Data; namespace DependencyObjectProperty { 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); } } }
則Binding可進一步寫成這樣:
private void button1_Click(object sender, RoutedEventArgs e) { Student stu = new Student(); stu.SetBinding(Student.NameProperty, new Binding("Text") { Source=textBox1 }); textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source=stu}); }
效果如下:
//---------------------------------------------------------
自定義依賴屬性也可以不需要手動進行聲明、注冊並使用CLR屬性進行封裝,只需要輸入propdp,同時連按兩次Tab,一個標准的依賴屬性(帶CLR屬性包裝)就聲明好了。
prop:CLR屬性
propa:附加屬性
propdp:依賴屬性
附加屬性也是一種特別的依賴屬性,顧名思義,附加屬性是說一個屬性本來不屬於某個對象,但是由於某種需求而被后來附加上。也就是把對象放入一個特定的環境后對象才具有的屬性,比如Canvas.Left DockPanel.Dock Grid.Column等。
聲明時一樣用public static readonly三個關鍵詞修飾。唯一不同就是注冊附加屬性使用的是名為RegisterAttached的方法,但參數與Register方法相同。附加屬性的包裝器也與依賴屬性不同,依賴屬性使用CLR屬性對GetValue和SetValue兩個方法進行包裝,附加屬性則使用兩個方法分別進行包裝。
其可由propa+tab+tab方便的生成。理解附加屬性的意義及使用場合即可。