7.WPF学习 - 属性

系列文章目录

1.WPF学习 - .NET相关知识
2.WPF学习 - XAML 之简单介绍
3.WPF学习 - XAML之基本语法
4.WPF学习 -XAML 之 x 名称空间
6.WPF学习 - Binding

因为binding中用到了属性的概念。属性非常重要,所以这里选择先学习属性。

回归下前面几章内容

  1. .NET相关知识 - WPF是建立在.NET framwork中的程序,内存管理、安全机制等需要依靠.NET framework.
  2. xaml简单介绍 - xaml是一种声明性语言,树形结构,一个标签代表一个对象,xml语法特性在xaml中可用。xaml将界面设计和逻辑代码剥离开
  3. XAML基本语法 - xaml中,通过标签中的内容可以看到标签的属性。首先需要将用到的程序集声明并引用进来-xmlns映射程序集。其次关于赋值:1)标签的简单元素赋值,比如长宽高,可以通过element=“value” 形式赋值,也可以通过标签式赋值,<a> a. = “value”</a>。2)标记拓展,通过element=“{xxxxx}”,这种值需要依赖/绑定在别的东西身上。3)事件处理器 -用户对控件的动作,添加上相应的处理事件。例如双击控件。4)代码后置- 使用<![CDATE[…]]>转义标签,以及x:Code 将.cs中的逻辑代码提取到xaml界面设计中。
  4. x名称空间 - x名称空间中的类型有:特征attribute,标记拓展,指令元素。特征-用于修改一些属性。标记拓展-赋值的一些特殊形式(不能直接=赋值),指令元素-当前代码另做处理。


前言

WPF中属性包括CLR属性(对private字段的包装,自带get、set),依赖属性(value依赖于别的CLR属性)
属性


一、属性的演变-CLR属性-private 字段的包装

类的作用是对散落在程序中的变量和函数进行归档封装并控制对他们的访问
被封装在类中变量称之为字段(Field),它表示的是类和实例的状态
被封装在类中的函数称之为方法(method),它表示类或实例的功能
字段和方法构成了最原始的面向对象封装
用private、public、protect来控制字段和方法的可访问性
用static来修饰,表示这个字段或方法是对整个类有意义,而不仅仅局限于单个实例

为了保证外部对变量的可访问性,同时实现正确、安全的操作字段,
将对字段的读写改成下面这种方法
1.将字段设置private,让外部不能它随意访问,保证了安全性
2.通过函数接口开放对字段的访问,并判断了条件。保证了可访问性和正确性

public class Student
{
	private int age;
	public int GetAge()
	{
		return age;
	} 
	public void SetAge(int Value)
	{
		if(Value >0 && Value <=100)
		{
			this.age = Value;
		}
		else
		{//错误处理
			throw new OverflowException("Age overflow");
		}
	} 
}

很多传统类库使用这种方法对数据封装和访问方法(比如MFC)
我们称这对Get/Set方法为private字段的安全包装

.NET Framework推出时,将Get/Set方法合并成了属性 。所以在.NET中可以采取下面方法读写age

public class Student
{
    private int age;
    public int Age
    {
        get{ return age; }
        set
        {
            if (value > 0 && value <= 100)
            {
                this.age = value;
            }
            else
            {
                //错误处理
                throw new OverflowException("Age overflow");
            }
        }
    }
}

通过反编译工具(iL_DASM)查看
Student中类自动编译成了两个函数,get_Age,set_Age
在这里插入图片描述

使用

对于CLR属性,我们可以像普通字段一样赋值、读取就好。

Student stu= new Student();
stu.Age=15;

内存分析

不管创建了多少实例,方法(函数)只有一个拷贝。相对于private字段,新增get、set属性后,内存并没有增加。

小结

在.NET Framework中,这种get/set 属性又称为CLR属性,公共语言运行时属性-
CLR属性特点

  • 是对private字段的安全访问包装
  • 每个CLR属性都包装着一个非静态(static)的字段

二、依赖属性 - value依赖于别的CLR属性

每一个CLR属性都包装着一个非静态的字段,字段会占4byte的内存。TextBox有138个属性,假设每个字段都占有4byte内存,那么创建10x1000个TextBox对象分配的内存=413810*1000=5.26M ,除了最常用的Text属性外,其余的不常用,意味着大多数内存都被浪费了。
为了避免这种浪费,就通过依赖属性来节约内存。

传统的.NET 开发中,一个对象所占用的空间,在调用new操作符进行实例化的时候就已经确定了。

而WPF 允许对象在被创建的时候并不包含用于存储数据的空间,只保留在需要用到数据时候能够获得默认值、借用其他对象数据或实时分配空间的能力。–这种对象称之为依赖对象,依赖对象中实时获取数据的能力称之为依赖属性。

依赖对象是依赖属性的宿主
依赖对象概念被DependencyObject类所实现
依赖属性概念被DependencyProperty类所实现

DependencyObject的源代码

public class DependencyObject : DispatcherObject
    {
        //设置依赖属性的本地值,该值由其依赖属性标识符指定。
        //   dp:要设置的依赖属性的id.
        //   value:The new local value.
        public void SetValue(DependencyProperty dp, object value);
        //返回system.windows.dependencyobject的此实例的依赖属性的当前有效值。
        public object GetValue(DependencyProperty dp);

DependencyProperty必须以DependencyObject为宿主,借助它的GetValue和SetValue方法进行读写

DependencyObject是WPF系统中相当底层的一个基类
在这里插入图片描述
从这个类继承图来看,WPF中所有控件都是依赖对象,也就是说

控件的很多属性在运行时再去实时获取数据
节省实例对内存的开销
属性值通过Binding依赖在其他对象身上

2.1 自定义依赖属性的声明和使用

两个步骤
1.在DependencyObeject派生类中声明public static修饰的DependencyProperty成员变量。并使用DependencyProperty.Register方法获得DependencyProperty的实例。
2.使用DependencyObject 的SetValue和GetValue方法,借助DependencyProperty实例来存取值

2.1.1 声明 - 实现自定义依赖属性

1.对象 – 定义一个Student类,继承依赖对象基类DependencyObject
2.属性 – 类中声明一个依赖属性(DependencyProperty )实例,

约定 将依赖属性实例的名称定为 xxxProperty

这个实例用**public static readonly**修饰,且不是用new,而是使用Register来实例化
三个参数含义分别为:
1.string类型,指明以哪个CLR属性来作为依赖属性的包装器,此处还没准备包装器,但将来会使用名为Name的CLR属性来包装它 --   此处即使没有包装器,也可以正常运行。CLR属性包装器是指对依赖属性进行get/set包装
2.依赖属性存储类型
3.依赖属性宿主
    public class Student : DependencyObject 
    {
        public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
    }

依赖属性

2.1.2 使用 SetValue&GetValue能对宿主中的依赖属性进行读写

基于上面的自定义属性,我们现在来设置和读取这个NameProperty
主要实现:通过点击button,将textbox1的内容读取,并设置到textbox2显示
1.在主窗口xaml中新增三个ui元素,button,textbox1,textbox2

<Window x:Class="TuneSightDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="67*"/>
            <RowDefinition Height="20*"/>
        </Grid.RowDefinitions>
        <Slider x:Name="slider1" HorizontalAlignment="Left" Margin="94,312,0,0" VerticalAlignment="Top" Width="120"/>
        <TextBox Name="text2"  Text="hei" HorizontalAlignment="Left" Margin="94,52,0,0" TextWrapping="Wrap"  VerticalAlignment="Top" Width="120"/>
        <local:MyButton Content="show" UserWindowType="{x:Type TypeName=local:MyWindow}" HorizontalAlignment="Left" Margin="5,5,0,0" VerticalAlignment="Top"/>
        <Button x:Name="DpButton" Content="DependencyProperty" Margin="300,100,250,150" Click="OnBnClickedDpButton"/>
        <TextBox Name="textBox1" HorizontalAlignment="Left" Margin="300,217,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
        <TextBox Name="textBox2" HorizontalAlignment="Left" Margin="300,262,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
    </Grid>
</Window>

2…cs中添加逻辑代码

    public partial class MainWindow : Window
    {
        public static string WindowTitle = "SmartQiu's Window Title";
        public static string WindowContent { get { return "QiuQiu is very smart!"; } }
        public MainWindow()
        {
            InitializeComponent();
        }
        private void OnBnClickedDpButton(object sender, RoutedEventArgs e)
        {
            Student stu = new Student();
            stu.SetValue(Student.NameProperty, this.textBox1.Text);//读取.textBox1并设置到NameProperty
            textBox2.Text = (string)stu.GetValue(Student.NameProperty);//读取NameProperty

        }
    }

验证效果:
在第一个文本框填入内容,点击DependencyPropertyButton,第二个文本框同步显示第一个文本框的内容

在这里插入图片描述
static readonly修饰依赖属性,那么不管有多少个Student实例,依赖属性都只有一个全局的。

2.2 依赖属性的原理

通过上面的自定义依赖属性,我们可以发现,依赖属性的值需要通过依赖对象的GetValue/SetValue函数进行读取。

2.2.1 依赖特性

        public MainWindow()
        {
            InitializeComponent();
            Student stu = new Student();
            Binding binding = new Binding("Text"){ Source = textBox1 };//新建binding实例,textBox1是数据源,并从数据源的Text属性中获取数据
            BindingOperations.SetBinding(stu,Student.NameProperty, binding);//将stu对象借助Binding实例依赖在textBox1上
            //textBox2.SetBinding (TextBox.TextProperty, binding);//也可以将textBox1和textBox2直接binding进行关联
       }

BindingOperations.SetBinding(stu,Student.NameProperty, binding); 展现了依赖特性
SetBinding 是FrameworkElement类中的方法 ---- 所以微软希望SetBinding的数据目标是UI元素


2.2.2 CLR属性包装器

上面第二个例子,可以发现,即使没有包装器的概念,依赖属性也可以正常运行。
在OnBnClickedDpButton函数中,通过DependencyObject的SetValue和GetValue方法对NameProperty进行读写,但是会比较麻烦。依赖属性也被直接暴露,可能会造成不安全的读写。

我们再次对依赖属性进行get/set包装 – 也称为CLR属性外包装,这样就可以安全的访问依赖属性了。

    public class Student : DependencyObject
    {
        public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
        //CLR属性包装器
        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
    }

同时有了这个包装,相当于为依赖属性对象准备了暴露数据的BindingPath。也就是说,依赖对象已经具备了扮演数据源和数据目标单双重角色的能力

尽管Student类没有实现INotifyPropertyChanged接口,但是当属性的值发生改变时,与之关联的Binding对象依然会得到通知。依赖属性默认带有这样的功能。–?因为register?

2.2.3 升级Student类

借用FrameworkElement类的SetBinding函数,用到Student类身上。
stu作为桥梁,利于Binding,采用textBox1-Text属性作为数据源,NameProperty依赖作为数据目标进行绑定。
同样textBox自身的TextProperty依赖属性,与stu的Name-CLR属性进行绑定

public MainWindow()
 {
     InitializeComponent();
     //以下实现数据传递textBox1->NameProperty->textBox2
     stu.SetBinding(Student.NameProperty, new Binding("Text") { Source = textBox1 });
     textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });
 }
    
public class Student : DependencyObject
{
    public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
    //CLR属性包装器
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
    //SetBinding包装
    public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding)
    {
        return BindingOperations.SetBinding(this, dp, binding);
    }
}

自定义依赖属性时,可以输入propdp,按下Tab键,系统自动出现标砖依赖属性的模板(带了CLR属性包装)

2.3 static readonly - 依赖属性是全局的

static:静态的、全局的
readonly:只读
依赖属性的值不存在依赖对象分配的内存中。WPF有一套机制来存取依赖属性的值。

DependencyProperty类中有一个static成员Hashtable,一旦程序运行,这个全局的Hashtable就会存在

2.3.1 DependencyProperty.Register(string name, Type propertyType, Type ownerType)

在源码中,所有的DependencyProperty.Register的方法重载最后都归结为对在源码中,所有的DependencyProperty.RegisterCommon方法的调用(了可以理解为是Register 方法的完整版))
创建一个Dependency实例,并用他的CLR属性名和宿主类型名生成hash code, 最后把hash code和DependencyProperty实例作为Key-Value存入 - 全局的、名为PropertyFromName的HashTable中

name:CLR属性的名字
propertyType:CLR属性类型(存储的值的类型)
ownerType:宿主类型(DependencyObject的类型)
功能:创建DependencyProperty实例并注册

  • 1.内部调用RegisterCommon函数
    • 1.1RegisterCommon函数,new一个FromNameKye对象
      • 1.1.1 FromNameKey 的构造函数,将name 和ownerType做异或运算得到一个HashCode
        • 如果使用同一个name和ownerType,第二次调用Register函数,那么程序会抛出异常
  • 2.检查是否提供了PropertyMetadata,如果没有就AutoGeneratePropertyMetadata生成默认的defaultMetadata
  • 3.通过new创建DependencyProperty实例
  • 4.DependencyProperty实例放入PropertyFromName table中,形成FromNameKey - DependencyProperty对应关系。就可以通过hashcode检索到DependencyProperty实例
  • 5.返回所创建的DependencyProperty实例
private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
        {
        	//1.生成hashcode
            FromNameKey key = new FromNameKey(name, ownerType);
            lock (Synchronized)
            {
                if (PropertyFromName.Contains(key))
                {
                    throw new ArgumentException(MS.Internal.WindowsBase.SR.Get("PropertyAlreadyRegistered", name, ownerType.Name));
                }
            }
            //2.生成PropertyMetadata
            if (defaultMetadata == null)
            {
                defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);
            }
            else
            {
                if (!defaultMetadata.DefaultValueWasSet())
                {
                    defaultMetadata.DefaultValue = AutoGenerateDefaultValue(propertyType);
                }

                ValidateMetadataDefaultValue(defaultMetadata, propertyType, name, validateValueCallback);
            }
 			//3.new DependencyProperty实例
            DependencyProperty dependencyProperty = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
            defaultMetadata.Seal(dependencyProperty, null);
            if (defaultMetadata.IsInherited)
            {
                dependencyProperty._packedData |= Flags.IsPotentiallyInherited;
            }
            if (defaultMetadata.UsingDefaultValueFactory)
            {
                dependencyProperty._packedData |= Flags.IsPotentiallyUsingDefaultValueFactory;
            }
			//4.注册到PropertyFromName table中
            lock (Synchronized)
            {
                PropertyFromName[key] = dependencyProperty;
            }

            if (TraceDependencyProperty.IsEnabled)
            {
                TraceDependencyProperty.TraceActivityItem(TraceDependencyProperty.Register, dependencyProperty, dependencyProperty.OwnerType);
            }
            return dependencyProperty;
        }

在这里插入图片描述
因为每个DependencyProperty实例创建的GlobalIndex是唯一的,所以GetValue和SetValue可以通过DependencyProperty实例来保存和读取值。

2.3.2 GetValue

public object GetValue(DependencyProperty dp)
{
//校验传入参数的有效性
    VerifyAccess();
    if (dp == null)
    {
        throw new ArgumentNullException("dp");
    }
//读取DependencyProperty数据
    return GetValueEntry(LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved).Value;
}
  • 1.EntryIndex entryIndex=LookupEntry(dp.GlobalIndex);
    -通过globalIndex(hashcode) 拿到依赖属性的入口
  • 2.GetValueEntry,得到EffectiveValueEntry实例(和DependencyProperty关联)
    • EffectiveValueEntry中有属性PropertyIndex属性 = DependencyProperty的GlobalIndex值
      每个DependencyObject实例都自带EffectiveValueEntry类型数组。要读取依赖属性时,就从这个数组里面去检索值。如果没有这个值,就返回依赖属性的默认值(有DefaultMetadata提供)

EffectiveValueEntry类中具有一个属性PropertyIndex,属性的值 = DependencyProperty的GlobalIndex
DependencyObject类中有一个成员变量

总结:
static修饰的DependencyProperty实例,实际用来检索真正的属性值,而不是存储值。
用来做检索键值的是依赖属性的GlobalIndex属性(本质是hash code)。而hash code是有CLR包装器和宿主类型名共同决定的,
为了保证GlobalIndex属性值的稳定性,所以用read only关键字进行修饰
在这里插入图片描述

SetValue

在这里插入图片描述

snippet 代码快速补全

快速创建依赖属性-propdp

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

附加属性

附加属性是指,把对象放入一个特定环境后,对象具有了原本没有的属性。
比如小明上学之后,xx学校xx班级 是他的附加属性。
在这里插入图片描述

附加属性的作用就是将属性与数据类型(宿主)解耦,让数据类型的设计更加灵活。
附加属性的本质是依赖属性,二者仅在注册和包装器上有一点区别

举例

在这里插入图片描述
快速创建依赖属性 - propa

自定义附加属性的声明和使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值