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

前面讲了,数据目标="{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源码分析:

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(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


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>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结