前面讲了,数据目标="{Binding 数据源}",绑定的数据源,这里我们讲得数据目标。
一、属性(Property)的来龙去脉。
1、变量、函数
数据(变量)+算法(函数)=程序
2、字段、方法
类的作用:把散落的程序中的变量和函数进行归档封装,并控制它的访问。被封装在类里的变量称为字段(Field),被封装在类里的函数称为方法(Method)。
我们使用private、public等修饰符来控制字段或方法的可访问性。使用static决定字段或方法是对类有意义,还是对类的实例有意义。
如:Age对类的实例有意义,Amount(总量)对类有意义。
c#语言规定:对类有意义的字段或方法使用static关键字修饰,称为静态成员,通过类名加访问操作符(即“.”操作符)可以访问它们。
对类的实例有意义的字段和方法不加static关键字,称为非静态成员或实例成员。
静态成员、非静态成员在内存中的结构?
静态字段在内存中只有一个拷贝,非静态字段则每个实例拥有一个拷贝。无论方法是否为静态的,内存只有一个拷贝。
3、属性是如何演变出来的?
CLR属性是对字段进行封装,控制字段的可访问性,有效性。不会增加内存负担,仅仅是个语法糖衣(Syntax Sugar)。
class Human { private int age; public int Age { //可访问性(只读) get { return age; } private set { //有效性 if (value >= 0 && value <= 100) { age = value; } else { throw new OverflowException("Age不在范围"); } } } }
二、依赖属性(Dependency Property)
依赖属性就是一种可以自己没有值,并能通过使用Binding 从数据源获得值(依赖在别人身上)的属性。拥有依赖属性的对象被称为“依赖对象”。与传统的CLR属性和面向对象思想相比依赖属性有很多新颖之处,
其中包括:
节省实例对内存的开销。
属性值可以通过Binding依赖在其他对象上。
具有内置的更改通知的支持(当源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性)。
1、依赖属性对内存的使用方式
传统.NET开发中,一个对象所占用的内存空间在调用new操作符进行实例化的时候就已经决定。
WPF允许对象在被创建的时候,并不包含字段所占用的空间,只保留需要用字段能够获得默认值、借用其它对象字段或实时分配空间的能力。这种对象称为依赖对象(Dependency Object),而它实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。
DependencyObject 依赖对象是WPF系统中相当底层的一个基类。WPF所有UI控件都是依赖对象,节省内存开销。
DependencyObject 继承树如下图结构:
public class DependencyObject : System.Windows.Threading.DispatcherObject { //DependencyObject具有GetValue或SetValue方法,以DependencyProperty作为参数 //GetValue通过DependencyProperty对象获取数据 public object GetValue(DependencyProperty dp) { } //SetValue通过DependencyProperty对象存储数据 public void SetValue(DependencyProperty dp,object value) { } }
实例一:依赖属性
定义依赖属性,必须以DependencyObject为宿主、借助它的SetValue和GetValue方法,写入或读取。
(1)、想定义依赖属性,类必须继承DependencyObject。
(2)、 变量由 public static readonly 三个修饰符修饰。
(3)、使用DependencyProperty.Register方法创建依赖属性的实例Name+Property,并对它进行注册。
//定义依赖属性,类必须继承 DependencyObject
public class Student : DependencyObject
{
//定义依赖属性必须是 public static readonly DependencyProperty Name+Property为后缀
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name",typeof(string),typeof(Student));
//CLR属性包装器名称(属性名称)、依赖属性类型(属性类型)、依赖属性关联到哪个类型(宿主类型)
public static readonly DependencyProperty AgeProperty=
DependencyProperty.Register("Age",typeof(int),typeof(Student),new PropertyMetadata(20));
//new PropertyMetadata(20) 数据默认元素据20(属性默认值)
}按钮调用
Student s = new Student();//创建Student实例 s.SetValue(Student.NameProperty,"111"); //SetValue方法将111,存储进依赖属性 TextBox1.SetValue(TextBox.TextProperty,s.GetValue(Student.NameProperty)); //GetValue 获取依赖属性的值
实例二:CLR属性包装器:以“实例属性”的形式向外界暴露依赖属性。
为依赖属性 添加一个 CLR属性包装器,有个这个包装,就相当于为依赖对象准备了用于暴露数据的 Binding Path。也就是说,现在的依赖对象已经具备了扮演数据源和数据目标双重角色的能力。值得注意的是,尽管Student类没有实现INotifyPropertyChanged 接口,当属性的值发生变化时与之关联的Binding对象依然可以得到通知。
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty,value); }
}
Student s=new Student();
s.Name="111";
TextBox1.text=s.Name;
实例三:借用FrameWorkElement类的 SetBinding方法,使Student类有绑定方法。(推荐)
// 摘要: // 创建 System.Windows.Data.BindingExpressionBase 的新实例,并将其与指定的绑定目标属性关联。 // // 参数: // target: // 绑定的绑定目标。 // // dp: // 绑定的目标属性。 // // binding: // 描述绑定的 System.Windows.Data.BindingBase 对象。 // // 返回结果: // 为指定的属性创建并与之相关联的 System.Windows.Data.BindingExpressionBase 的实例。System.Windows.Data.BindingExpressionBase // 类是 System.Windows.Data.BindingExpression、System.Windows.Data.MultiBindingExpression // 和 System.Windows.Data.PriorityBindingExpression 的基类。 // // 异常: // System.ArgumentNullException: // target 参数不能为 null。 // // System.ArgumentNullException: // dp 参数不能为 null。 // // System.ArgumentNullException: // binding 参数不能为 null。 public static BindingExpressionBase SetBinding(DependencyObject target,DependencyProperty dp,BindingBase binding)
BindingOperations.SetBinding(this,dp,binding)
//数据源 public class Student : DependencyObject { //小技巧:输入 propdp,按Tab键。可以快速建依赖属性 //CLR属性包装器 public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty,value); } } //依赖属性 public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name",typeof(Student)); //借用 SetBinding包装 public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding) { return BindingOperations.SetBinding(this,binding); } }
Student stu; public Window1() { InitializeComponent(); stu = new Student(); //Student的Name属性 关联 textBox1的Text属性 stu.SetBinding(Student.NameProperty,new Binding("Text") { Source=textBox1}); //textBox2的Text属性 关联 Student的Name属性 textBox2.SetBinding(TextBox.TextProperty,new Binding("Name") { Source = stu }); }
三、依赖属性存取的秘密
1、DependencyProperty.Register 源码分析
(1)、创建一个DependencyProperty 实例,并用CLR属性包装器名和宿主类型进行异或生成键(key),把DependencyProperty 实例存到Hashtable(PropertyFromName) 中
private static HashtablePropertyFromName=newHashtable();
DependencyProperty dp=newDependencyProperty(name,PropertyType,ownerType,defaultMetadata,validateValueCallback);
PropertyFromName[key]=dp;
(2)、最后返回DependencyProperty 实例
return dp;
2、DependencyObject的GetValue、SetValue
GetValue源码分析:
//dp.GlobalIndex 是:CLR属性包装器名和宿主类型进行异或生成键(key)
EntryIndex entryIndex=LookupEntry(dp.GlobalIndex);
//每个依赖对象实例都自带一个EffectiveValueEntry 类型数组(存取值),当某个依赖属性的值要被读取时,算法会从这个数组中检索值
//如果数组中没有包含这个值,算法会返回依赖属性的默认值(这个值由依赖属性的DefaultMetadata来提供的)
//static关键字所修饰的依赖属性对象,用来检索数据。
//为了保障GlobalIndex稳定性,使用readonly关键字修饰。
EffectiveValueEntry valueEntry=GetValueEntry(entryIndex,null,RequestFlags.FullyResolved)
return valueEntry.Value;
依赖属性读取是优先级控制的,由先到后依次是:
SetValue源码分析:
四、附加属性(Attached Properties)
如:
<Canvas Margin="10">
<TextBox Canvas.Top="0"/>
<TextBox Canvas.Top="30"/>
<TextBox Canvas.Top="60"/>
</Canvas>
实例:人在学校获得班级属性:
DependencyProperty.RegisterAttached()
class School : DependencyObject { //输入 propa,按Tab键。继续按Tab键,可以在几个空缺间轮换并修改,直到按下Enter键。 public static int GetGrade(DependencyObject obj) { return (int)obj.GetValue(GradeProperty); } public static void SetGrade(DependencyObject obj,int value) { obj.SetValue(GradeProperty,value); } //使用RegisterAttached方法注册附加属性(班级Grade) public static readonly DependencyProperty GradeProperty = DependencyProperty.RegisterAttached("Grade",typeof(School),new UIPropertyMetadata(0)); } class Human : DependencyObject { }
Human human = new Human(); School.SetGrade(human,6); int grade= School.GetGrade(human); MessageBox.Show(grade.ToString());
六、用户控件 依赖属性
效果:动画数字从100到200
代码:
1、用户控件 ShowNumberControl
<UserControl x:Class="WpfApplication1.ShowNumberControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <Label x:Name="numberDisplay" Height="50" Width="200" Background="LightBlue"/> </Grid> </UserControl>
创建依赖属性CurrentNumberProperty。
当依赖属性的值发生改变时,先验证值是否正确。
如果正确,就将依赖属性最新的值赋值给Label
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 WpfApplication1 { /// <summary> /// ShowNumberControl.xaml 的交互逻辑 /// </summary> public partial class ShowNumberControl : UserControl { public ShowNumberControl() { InitializeComponent(); } public int CurrentNumber { get { return (int)GetValue(CurrentNumberProperty); } set { SetValue(CurrentNumberProperty,value); } } //依赖属性 public static readonly DependencyProperty CurrentNumberProperty = DependencyProperty.Register("CurrentNumber",typeof(ShowNumberControl),new UIPropertyMetadata(100,new PropertyChangedCallback(CurrentNumberChanged)),new ValidateValueCallback(ValidateCurrentNumber) ); //验证依赖属性值 public static bool ValidateCurrentNumber(object value) { if (Convert.ToInt32(value) >= 0 && Convert.ToInt32(value) <= 500) return true; else return false; } //当依赖属性更改后,将用户控件中的Label设置最新依赖属性的值。 public static void CurrentNumberChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) { ShowNumberControl c = (ShowNumberControl)d; Label theLabel = c.numberDisplay; theLabel.Content = e.NewValue.ToString(); } } }
new UIPropertyMetadata(100,new PropertyChangedCallback(CurrentNumberChanged))
依赖属性的默认值 100。
属性更改后,委托执行方法CurrentNumberChanged
<Window x:Class="WpfApplication1.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myControl="clr-namespace:WpfApplication1"
Title="Window3" Height="300" Width="300">
<Grid>
<myControl:ShowNumberControl x:Name="myShowNumberControl" CurrentNumber="100">
<myControl:ShowNumberControl.Triggers> Triggers触发器
<EventTrigger RoutedEvent="myControl:ShowNumberControl.Loaded"> EventTrigge事件触发器
<BeginStoryboard> BeginStoryboard 开始动画板
<Storyboard TargetProperty="CurrentNumber"> TargetProperty 动画板目标属性 <Int32Animation From="100" To="200" Duration="0:0:10"/> </Storyboard> </BeginStoryboard> </EventTrigger> </myControl:ShowNumberControl.Triggers> </myControl:ShowNumberControl> </Grid> </Window>