《深入浅出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》笔记——事件篇

原文地址:https://www.cnblogs.com/lzhp/p/2683200.html