《深入淺出WPF》筆記——屬性篇


  上兩篇的記錄重在記錄綁定的源(Source)和路徑(Path),本篇主要記錄一下目標(Target)的屬性。

一、屬性與讀寫方法

  在面向對象的程序設計中,一個類經常會有私有字段,屬性,方法。由於字段的訪問權限通常是private,所以要讀寫字段就要用到方法或者屬性,用方法實現讀寫的寫法:

    public class Person
    {
        private int age;
        //下面為讀寫方法,適當的時候可在方法內部加相應的代碼
        public int GetAge()
        { 
            return this.age;
        }
        public void SetAge(int value)
        {
            this.age = value;
        }
    }

下面是用屬性實現讀寫的代碼:

    public class PersonTest
    {
        private int age;

        //下面為屬性,適當的時候可在方法內部加相應的代碼
        public int Age
        {
            get { return this.Age;}
            set { this.Age = value; }
        }
    }

  到底讀寫方法和屬性有什么區別和聯系呢?其實開始的面向對象是只有方法,沒有屬性的,后來感覺GetAge()和SetAge()這樣寫太分散,才引入了屬性。具體的聯系我們可以通過IL來查看。為了在同一個類上面看到結果,我把讀寫方法和屬性都寫在了PersonTest類里面。

    public class PersonTest
    {
        private int age;
        //下面為方法
        public int GetAge()
        { 
            return this.age;
        }
        public void SetAge(int value)
        {
            this.age = value;
        }
        //下面為屬性
        public int Age
        {
            get { return this.Age;}
            set { this.Age = value; }
        }
    }

IL的結果如圖1:

圖1

  通過IL工具我們看到:屬性也被編譯新的方法。說明屬性就是方法的另外一種寫法。下面讓我們了解一下非靜態類和實例的非靜態方法和字段的存儲,對於一個非靜態類的字段每實例化一個對象,內存就准備一些空間,用來存儲字段的值。這樣的話,肯定會帶來資源的浪費,因為有的字段是不怎么常用,但是實例化的話還是要照類的標准去實例化,每個字段都要按一定的方式得到其值。為了解決這個問題,我們就引出依賴屬性。

二、依賴屬性

  在記錄綁定的時間,有提到過DataContext為依賴屬性,下面就詳細記錄一下依賴屬性。依賴屬性和“傳統”的屬性(CRL屬性)相比的新穎之處:1.因為其值依賴在別的對象(Source)上面,所以更節省實例化對內存的開銷。2.屬性值可以通過Binding依賴在其他對象上。下面解釋一下依賴對象和目標對象,以便更好的理解依賴屬性。簡單的說,依賴對象就是擁有依賴屬性需要的值的對象,通常是綁定的源(Source);目標對象(Target)是依賴屬性的擁有者。為什么WPF里面的UI可以作為依賴對象呢?我們來看圖1:

圖1

從圖1(Control下面的類沒有畫出來)可以看出來,所有的UI控件其基類都為DependencyObject。那么依賴屬性是怎么通過目標對象實現讀取的呢?其實和傳統的屬性代碼(只是代碼)很像。

   public class DependencyObject : DispatcherObject
    {
        public object GetValue(DependencyProperty dp);
        public void SetValue(DependencyProperty dp, object value);
    }

 只看這段代碼我覺得還是看不懂,現在我們就通過一個例子來展示怎么聲明和使用依賴屬性。

聲明依賴屬性:

    public class Student : DependencyObject //聲明一個對象,並繼承DependencyObject類
    {
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Student));
       //NameProperty為命名規范,與下面括號里的第一個參數(我們以前用的CLR屬性)一致,然后加上Property。第二個參數是“字段”的類型,第三個為依賴屬性的宿主(目標Target)的類型,還有第四
       //個參數,在此不作說明,具體用到再去研究 }

然后我們就可以用上面的兩個方法去獲取和設置依賴屬性的值了。為了能像CLR屬性讀寫依賴屬性的值我加了一個外包裝:

    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));
    }

下面就讓我們利用它一下吧!把Student實例stu的NameProperty依賴屬性綁定到TextBox的Text屬性上面。在此我們引入一個操作Banding的類public static class BindingOperations,這個類提供了一個靜態方法:

        public static BindingExpressionBase SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding);

所以我就可以這樣寫了:

stu = new Student();
Binding binding = new Binding("Text") { Source = txt1 };
BindingOperations.SetBinding(stu,Student.NameProperty,binding); 

寫到這里,由於依賴屬性默認帶有“INotifyPropertyChange”的功能,也是天生的合格數據源,所以我們這時就可以獲取stu.Name.ToString()的值了。但以前用的Binding和上面的形式有點不一樣,如果把txt2的TextProperty綁定到txt1的Text屬性上面的格式是這樣寫的:

this.txt2.SetBinding(TextBox.TextProperty, new Binding("Text") { Source = txt1 });

   為了實現像上面的那樣綁定我們還需要加工一下Student對象,根據上面的靜態方法,我們能不能再Student里面寫一個SetBinding方法,答案是Yes。下面看一下完整的Student類的代碼:

    public class Student : DependencyObject
    {
       //定義一個屬性來讀寫依賴屬性讀寫的值
        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Student));
        public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
        {
            return BindingOperations.SetBinding(this, dp, binding);
        }
    }

OK,有了現在的類就可以使用像UI控件一樣的綁定了。

stu.SetBinding(Student.NameProperty, binding);

   這樣,我們就可以像操作UI控件的依賴屬性一樣,輕松的對Student類進行操作,甚至還可以把Student的stu實例作為綁定源,讓另外一個控件的依賴屬性綁定到他的Name依賴屬性上面。下面我把完整的代碼貼出來,此代碼主要實現的是Student類的依賴屬性綁定在TextBox類txt1的Text屬性上面,TextBox類txt2的TextProperty依賴屬性綁定在Stu的Name上。

XAML
<Window x:Class="Chapter_05.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="162" Width="343">
    <StackPanel Orientation="Vertical" >
        <TextBox Name="txt1" Margin="5" BorderBrush="Green"/>
        <TextBox  Name="txt2" Margin="5" BorderBrush="Green"  />
        <Button Content="Button" Margin="5" Click="Button_Click" />
    </StackPanel>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Chapter_05
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        Student stu;
        public MainWindow()
        {
            InitializeComponent();
            stu = new Student();
            Binding binding = new Binding("Text") { Source = txt1 };
            // stu.SetValue(Student.NameProperty,binding);
            //BindingOperations操作綁定的靜態類
            //下面區分兩個SetBinding方法,返回值都為BindingExpressionBase類型,
            //一個是FrameworkElement的方法,一個是BindingOperations的方法
            //BindingOperations.SetBinding(stu,Student.NameProperty,binding);
            stu.SetBinding(Student.NameProperty, binding);
            this.txt2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //有了Name這個CLR屬性,下面的兩局的效果是一樣的
            MessageBox.Show(stu.Name.ToString());
            //MessageBox.Show(stu.GetValue(Student.NameProperty).ToString());
        }
    }
    public class Student : DependencyObject
    {
       //定義一個屬性來讀寫的依賴屬性讀寫的值
        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Student));

        public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
        {
            return BindingOperations.SetBinding(this, dp, binding);
        }
    }
}

運行會發現txt1的值會帶動txt2的值的改變。單擊按鈕會出現txt1的值。效果如圖2:

圖2

  依賴屬性其實可以理解為Binding目標的屬性,與源中的Path是相對的。一個依賴屬性可能會有多個數據源,但是起最終決定作用的只有一個。具體的的可以參考jv9的文章:http://www.cnblogs.com/jv9/archive/2012/06/07/2539208.html。如果有問題的話,可以留言。到了本小節的最后,關於聲明依賴屬性還有個技巧,那就是在vs2010平台上一個類下面,直接鍵入“propdp”,然后按兩次Tab鍵,就可自動生成一個模板,適當的修改一下,就可以輕松的完成其聲明。記錄到這里,回頭看看發現依賴屬性是“靜態的、只讀的”,這里修飾的DependencyProperty內部的數據,在此不作過多的解釋。 

三、附加屬性

  在了解附加屬性之前,我們先通過一個例子來說明附加屬性的意義所在。假設對於一個大學生來說,年級和班級對學校來說是比較有用的,如果你出去找工作,那么年級和班級顯得就不再那么重要了。這時我們設計大學生類的時間是否讓年級和班級屬性給設計出來呢,如果設計出來的話會,在工作類里面幾乎沒有用途,是不是浪費了一定的資源。現在想如果是有一種方法能讓學校類里面有年級和班級屬性呢,如果是每一個人都給予年級和班級屬性的話,對於學校的教務處的老師來說的話,年級和班級屬性顯得有點多余。所以現在就出現了一個類,根據角色來確定是否有其屬性,根據角色,讓你自己去加。如果是學生,那么就自己去找班級和年級,如果是教務處老師的話就不用附加了。這樣不就達到了我想要的,用不用自己選擇。根據前面的依賴屬性,我們想如果能和依賴屬性一樣,可以依賴其他源,那么不是又能起到節省資源的效果。這個就是我們要介紹的附加屬性。先通過XAML代碼來有個大概的認識。

<Window x:Class="Chapter_05.priorityOfDependProperty"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="priorityOfDependProperty" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <TextBox Grid.Column="1" Grid.Row="1" Margin="5" BorderBrush="Green"/>
    </Grid>
</Window>

在上面代碼中,TextBox本來是沒有 Grid.Column屬性的,但是因為在Grid里面的原因就被可以附加此屬性。下面看一下怎么讓年級屬性附加給學生的。先通過學校類來看一下附加屬性的聲明(也可以在類下面直接輸入propa,按兩下Tab鍵,然后再稍作修改就OK了): 

    public 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));
        
    }

   由聲明就可以看出來附加屬性也是DependencyProperty類型的變量。和依賴屬性的CLR屬性有點差別。但是里面的代碼卻不難看懂。如果定義學生的話,由GetGrade(DependencyObject obj)看出,一定要讓其繼DependencyObject類。下面給出學生的定義。

    public class Student : DependencyObject
    {
        
    }

然后任意來一個學生,需要找自己的年級,那么就找學校為其安排年級和然后問學校自己的年級是多少。 下面給出設置和獲取的代碼:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Student s = new Student();
            School.SetGrade(s,6);
            int grade = School.GetGrade(s);
            MessageBox.Show(grade.ToString());
        }

  通過上面的一個實例,我們稍微總結一下附加屬性的使用:附加屬性的是通過另外一個類附加進來的屬性,如果想具有附加的屬性的對象(上面指的是Student)要繼承DependencyObject這個類。除此之外,附加屬性的值是可以像依賴屬性一樣通過綁定獲得的。下面給出一個例子,矩形的在畫布上的位置隨着兩個Slider的值的改變而改變。代碼如下: 

<Window x:Class="Chapter_05.Test"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test" Height="300" Width="300">
    <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="ret"  Width="30" Height="30" Fill="Green" Canvas.Left="{Binding ElementName=sliderX,Path=Value}" Canvas.Top="{Binding ElementName=sliderY
    ,Path=Value}"/> </Canvas> </Window>

四、總結

  本文主要記錄了依賴屬性和附加屬性,關鍵是在於理解其聲明以及在綁定中的位置以及與傳統的屬性的區別。如果有不足的地方,請多多指點!下一篇:《深入淺出WPF》筆記——事件篇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM