WPF Property属性

来源:互联网 发布:基金是什么 知乎 编辑:程序博客网 时间:2024/05/16 11:20

1. 属性的来龙去脉


    被封装在类里的变量称为字段,它表示的是类或实例的状态;被封装在类里的函数叫做方法,它表示的是类或实例的功能。字段和类构造出了最原始的面向对象封装,这时候的面向对象中还不包含事件,属性等概念。


    静态字段在内存中只有一个拷贝,非静态字段则每个实例都有一个拷贝,无论方法是静态还是非静态,在内存中都只有一个拷贝,区别只是你能通过类名来访问存放内存中的指令还是通过实例来访问。


    字段被封装在实例里,要么能被外接访问, 要么不能,这种直接把数据暴露给外界的做法很不安全,很容易就把错误的值写入字段。如果在每次写入字段的时候先判断一下值的有效性又会增加冗余的代码并违反了面向对象要求的“高内聚”的原则。


   于是,当Framwork推出以后,微软进一步对Get/Set这对方法进行了封装合并成了属性(Property)。使用属性的时候,格式上很像使用非private字段,保证了语义上的顺畅,同时又不失Get/Set方法的安全性,代码也变的更加紧凑。

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

    那么是不是每个对象的CLR属性也会多占一点内存呢?想得到这个答案,使用IL反编译器打开编译结果:


    

    通过IL工具我们看到:属性也被编译新的方法。说明属性就是方法的另外一种写法。对于一个非静态类的字段每实例化一个对象,内存就准备一些空间,用来存储字段的值。这样的话,肯定会带来资源的浪费,因为有的字段是不怎么常用,但是实例化的话还是要照类的标准去实例化,每个字段都要按一定的方式得到其值。为了解决这个问题,我们就引出依赖属性。


2. 依赖属性


    依赖属性就是可以自己没有值,并能够通过Binding从数据源获取值(依赖在别人身上)的属性。拥有依赖属性的对象被称为“依赖对象”。与传统的CLR属性和面向对象相比依赖属性有很多新颖之处,其中包括:
节省实例对内存的开销。
属性值可以通过Binding依赖在其它对象上。


    让我们去思考一个现实世界中存在的问题:一个登山队员,他的全套装备有很多,包括登山服、登山靴、登山仗、护目镜、绳索、无线电、水、食品甚至还有氧气瓶等。倘若是去等珠穆朗玛峰,这些装备都要带上,要是去登香山呢?如果也背着氧气瓶岂不怪哉!所以,实际的一点办法就是---用得着的就带上,用不着的就不带,有必要的时候可以借别人的用一下。


    其实,这就是WPF中依赖属性的原理。传统的.NET开发中,一个对象所占用的内存空间在调用new操作符进行实例化的时候就已经决定了,而WPF允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据的时候能够获得默认值、借用其它对象的数据或者实时分配空间的能力----这种对象称为依赖对象而他这种实时获取数据的能力则依靠依赖属性来实现。在WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来,才能形成完整的Binding目标被数据所驱动。


    在WPF系统中,依赖对象的概念被DependencyObject类所实现。依赖属性的概念则由DependencyProperty来实现。DependencyObject具有GetValue和SetValue两个方法。WPF的所有控件都是依赖对象。WPF的类库在设计的时候充分利用了依赖属性的优势,UI控件的绝大多数属性已经依赖化了。




    DependencyProperty必须以DependencyObject作为宿主,借助它的SetValue和GetValue进行记录和读取。因此,想使用自定义的DependencyProperty,宿主一定是DependencyObject的派生类。DenpendencyProperty实例声明特点很鲜明----引用变量由public static readonly三个修饰符进行修饰,实例并非使用new操作符得到而是使用DependencyProperty.Register方法实现。使用一个命名约定----成员变量的名字需要加入Property的后缀表明它是一个依赖属性。我们打算用这个依赖属性存学生的姓名,所以把它命名为NameProperty。


    使用DependencyProperty.Register方法创建,分析一下这三个参数:


    第一个参数为string类型,用这个参数指明哪个CLR属性作为这个依赖属性的包装器,或者说此依赖属性支持的是哪个CLR属性。
包装器的作用是以“实例属性”的形式向外界暴露依赖属性,这样一个依赖属性才能成为数据源的path

    第二个参数用来指明此依赖属性用来存储什么类型的值,学员的姓名是string类型的值,所以这个参数被赋值为typeof(string)。
    第三个参数用来指明此依赖属性的宿主是什么类型,或者说DependencyProperty.Register方法将这个依赖属性关联到哪个类型上。本例的意图是为Student类准备一个可依赖的名称属性,所以需要将NameProperty和Student进行关联。因此该参数被赋值为typeof(Student)。


实例:我们使用Binding将Student关联到TextBox1上,在把TextBox2的值关联到Student对象上形成Binding链。

XAML:

<Window x:Class="第七章属性.wnd72"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        Title="MainWindow" Height="120" Width="525">    <StackPanel Background="LightSlateGray">        <TextBox x:Name="_txtBox1" Height="28" Margin="5"/>        <TextBox x:Name="_txtBox2" Height="28" Margin="5"/>    </StackPanel></Window>

C#:

    /// <summary>    /// 声明类为依赖对象 -- 依赖属性必须基于依赖对象宿主    /// </summary>    public class Student:DependencyObject    {        /// <summary>        /// CLR属性读写的依赖属性的值        /// 输入"propdp", 生成依赖属性模版, 通过"Tab"键调节名称即可        /// </summary>        public string Name        {            get { return (string)GetValue(NameProperty); }            set { SetValue(NameProperty, value); }        }        /// <summary>        /// NameProperty为命名规范,与下面括号里的第一个参数(我们以前用的CLR属性)一致,然后加上Property。        /// 第二个参数是“字段”的类型        /// 第三个为依赖属性的宿主(目标Target)的类型        /// </summary>        public static readonly DependencyProperty NameProperty =            DependencyProperty.Register("Name", typeof(string), typeof(Student));        /// <summary>        /// 为了方便操作        /// </summary>        public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)        {            return(BindingOperations.SetBinding(this, dp, binding));         }    }    /// <summary>    /// wnd72.xaml 的交互逻辑    /// </summary>    public partial class wnd72 : Window    {        Student stu = new Student();        public wnd72()        {            InitializeComponent();            stu.SetBinding(Student.NameProperty, new Binding("Text") { Source = _txtBox1 });            _txtBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });        }    }

    运行程序的时候,当TextBox1的时候中输入字符的时候,TextBox2也会同步显示。当然,此时的Student对象的Name属性值也同步发生变化了。




    在一个类中声明依赖属性并不需要手动进行声明、注册并使用CLR属性进行封装,只需要输入propdp,VisualStudio提示列表就会有一项高亮显示,连续按两次Tab键,一个标准的依赖属性(带CLR属性包装)就声明好了,再按动Tab键,可以在提示环境中修改依赖属性的各个参数。这个功能称为snippet(称为代码模板或代码片段),这是VisulaStudio中所有非简化版本自带的功能,多多掌握这个功能可以大大的提高编码速率和降低错误率。


    值得注意的是,尽管Student类没有实现INotifyPropertyChanged接口,当属性的值发送改变时与之关联的binding对象依然可以得到通知,依赖属性默认的带有这种功能,天生就是合格的数据源


1.3 附加属性


    附加属性就是说一个属性本来不属于某个对象,但由于某种需求后来又被附加上了。也就是说把对象放入一个特定的环境对象才能拥有该属性,这种属性就是附加属性。实际开发工作中,我们经常会遇到这种情况,比如一个名为Human的类,它有可能被与学校相关的工作流用到(记录它的班级,年级等信息),也有可能被与学校相关的工作流用到(记录他的部门,项目)。


那么设计类的时候我们是不是要这样做呢?显然占用更多内存、维护很麻烦。

public class Human      {          public int Id { get; set; }          //For School Overflow          public int ClassId { get; set; }          public int MajorId { get; set; }          public int GradeId { get; set; }            //For comapany OverFlow          public int DepartmentId { get; set; }          public int ProjectId { get; set; }      }  

   附加属性的本质就是依赖属性。二者仅在包装器和注册上有一点区别。前面已经讲过,VS里面自带的有用于快速创建依赖属性的snippet和propdp,现在使用另外一个snippet用于快速创建附加属性propa。当VS出现高亮显示的时候连续按两次Tab键,一个附加属性框架就准备好了。继续按Tab键可以在几个空缺间轮换并修改,直至按下Enter键。


实例,使用School对Student添加附加属性,并弹出值:

    /// <summary>    /// 附加属性 -- 基于特定的环境才会有的属性    /// </summary>    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));    }    /// <summary>    /// wnd73.xaml 的交互逻辑    /// </summary>    public partial class wnd73 : Window    {        Student stu = new Student();        public wnd73()        {            InitializeComponent();            School.SetGrade(stu, 6);            string grade =  School.GetGrade(stu).ToString();            MessageBox.Show(grade);        }    }
    

    可以明显看出,GradeProperty就是一个DependencyProperty类型的成员变量,声明时一样使用了public static readonly修饰符。唯一的不同就是注册附加属性的时候使用的是RegisterAttached方法,但参数却与Register方法无异。附加属性的包装器与依赖属性的包装器不同---依赖属性使用CLR属性对GetValue和SetValue两个方法进行包装。附加属性则使用两个方法分别进行了包装----这样做完全是在使用的时候保持语句行文上的流畅。

1 0
原创粉丝点击