您的位置:首页 > 产品设计 > UI/UE

WPF 依赖属性,用户控件依赖属性(DependencyProperty 依赖属性、GetValue() SetValue() CLR属性包装器、SetBinding 数据绑定)

2016-11-07 15:38 573 查看
前面讲了, 数据目标="{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(string),typeof(Student));

//借用 SetBinding包装
public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
{
return BindingOperations.SetBinding(this,dp,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 Hashtable PropertyFromName=new Hashtable();

DependencyProperty dp=new DependencyProperty(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,dp,null,RequestFlags.FullyResolved)

return valueEntry.Value;

依赖属性读取是优先级控制的,由先到后依次是:



SetValue源码分析:

EffectiveValueEntry数组,修改值,存储值。

四、附加属性(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(int), 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(int), 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。

属性更改后,委托执行方法 [b]CurrentNumberChanged[/b]

2、窗体调用 用户控件

<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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: