一、CLR 属性

​ 程序的本质是“数据+算法”,或者说用算法来处理数据以期得到输出结果。在程序中,数据表现为各种各样的变量,算法则表现为各种各样的函数(操作符是函数的简记法)。

类的作用是把散落在程序中的变量和函数进行归档封装并控制它们的访问。被封装在类里的变量称为字段(Field),它表示的是类或实例的状态;被封装在类里的函数称为方法(Method),它表示类或实例的功能。

​ 字段(Field)被封装在实例里,要么能被外界访问(非 Private修饰),要么不能(使用 Private 修饰),这种直接把数据暴露给外界的做法很不安全,很容易把错误的数值写入字段。为了解决此问题,.NET Framework 推出了属性(Property),这种 .NET Framework 属性又称为 CLR 属性。

​ 属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。 属性可用作公共数据成员,但它们实际上是称为访问器的特殊方法。 这使得我们不仅可以轻松访问数据,还有助于提高方法的安全性和灵活性。具体使用如下:

private double _seconds;

   public double Hours
{
get { return _seconds / 3600; }
set {
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException(
$"{nameof(value)} must be between 0 and 24."); _seconds = value * 3600;
}
}

二、依赖属性(Dependency Property)

​ 实例中每个 CLR 都包装着一个非静态的字段(或者说由一个非静态的字段在后台支持)。如果一个 TextBox 有 100 个属性,每个属性都包装着一个 4 byte 的字段,那如果程序运行创建 10000 个 TexBox 时,属性将占用 100*4**10000≈3.8M 的内存。在这 100 个属性中,最常用的是 Text 属性,这意味着大多数的内存都会被浪费掉。为了解决此问题,WPF 推出了依赖属性。

依赖属性(Dependency Property),就是一种可以自己没有值,但能通过 Binding 从数据源获得值(依赖在别人身上)的属性。拥有依赖属性的对象被称为“依赖对象”。

​ WPF 中允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据时能够获得默认值、借用其他对象数据或实时分配空间的能力——这种对象被称为“依赖对象(Dependency Object)”,这种实时获取数据的能力依靠依赖属性(Dependency Property)来实现。

​ WPF 中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来,才能形成完整的 Binding 目标被数据所驱动。依赖对象的概念由 DependencyObject 类实现,依赖属性的概念由 DependencyProperty 类实现。DependencyObject 类具有 GetValue 和 SetValue 两个方法。具体实现一个依赖属性如下图所示(在 Visual Studio 中可以使用 “propdp” 按 Tab 键快捷生成):

 public class StudentObject : DependencyObject
{
// CLR包装
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
} // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(StudentObject), new PropertyMetadata(0)); }

​ WPF 中控件的属性大多数都为依赖属性,例如,Window 窗体的Title 属性,我们查看代码后如下:

 /// <summary>获取或设置窗口的标题。</summary>
/// <returns>
/// 一个 <see cref="T:System.String" /> ,其中包含窗口的标题。
/// </returns>
[Localizability(LocalizationCategory.Title)]
public string Title
{
get
{
this.VerifyContextAndObjectState();
return (string) this.GetValue(Window.TitleProperty);
}
set
{
this.VerifyContextAndObjectState();
this.SetValue(Window.TitleProperty, (object) value);
}
}

​ WPF 中控件的继承关系: Control -----> FrameworkElement -----> UIElment -----> Visual -----> DependencyObject 。即 WPF 中所有 UI 控件都是依赖对象,UI 控件的大多数属性都已经依赖化了。

​ 当我们为依赖属性添加 CLR 包装时,就相当于为依赖对象准备了暴露数据的 Binding Path,即该依赖对象具备扮演数据源(Source)和数据目标(Target)的能力。该依赖对象虽然没有实现 INotifyPropertyChanged 接口,但当属性的值发生改变的时候与之关联的 Binding 对象依然可以得到通知,依赖属性默认带有这样的功能,具体如下:

​ 我们声明一个自定义控件,控件的依赖属性为 DisplayText:

 public class MorTextBox : TextBox
{
public string DipalyText
{
get { return (string)GetValue(DipalyTextProperty); }
set { SetValue(DipalyTextProperty, value); }
} // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DipalyTextProperty =
DependencyProperty.Register("DipalyText", typeof(string), typeof(MorTextBox), new PropertyMetadata("")); public new BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
{
return BindingOperations.SetBinding(this, dp, binding);
}
}

​ 我们先把该依赖属性作为数据目标获取第一个 TextBox 的 Text 属性,然后把自己的 DisplayText 依赖属性的值作为第二个 TextBox 的 Text 属性的数据源:

  <StackPanel>
<TextBox Margin="5" Height="50" x:Name="t1"></TextBox>
<!--自定义依赖属性作为 Target-->
<local:MorTextBox x:Name="t2" Visibility="Collapsed" DipalyText="{Binding ElementName=t1,Path=Text,UpdateSourceTrigger=PropertyChanged}"></local:MorTextBox>
<!--自定义依赖属性作为 Source-->
<local:MorTextBox Margin="5" Height="50" Text="{Binding ElementName=t2,Path=DipalyText,UpdateSourceTrigger=PropertyChanged}"></local:MorTextBox>
</StackPanel>

​ 当我们运行程序后,第二个 TextBox 的数值随着第一个 TextBox 数值的改变而改变。

三、附加属性(Attached Properties)

​ 实际开发中,我们会经常遇到这样的情况,一个人在学校的时候需要记录班级等信息,在公司需要记录职业等信息,那么如果我们在设计 Human 类的时候,在类里面直接定义 Grade、Position 属性合适吗?

​ 显然不合适!首先,当我们在学校上学的时候完全用不到公司等信息,那么Position 所占的内存就被浪费了。为了解决此问题,我们首先想到依赖属性,但解决了内存浪费问题,还存在一个问题,即一旦流程改变,那么 Human 类就需要做出改动,例如:当我们乘车的时候,有车次信息;去医院看病的时候,有排号信息等。这意味着应用场景的不断变化,导致我们所属的信息不断发生变化。为了解决此问题,.NET 推出了附加属性(Attached Properties)。

附加属性(Attached Properties)是说一个属性本来不属于某个对象,但由于某种需求而被后来附加上。也就是说把对象放入一个特定环境后对象才具有的属性(表现出来就是被环境赋予的某种属性)。上述例子,我们可以使用附加属性去解决这个问题(添加附加属性时,可以在 Visual studio 中输入 "propa" 然后按 Tab 键快捷生成):

 class Human : DependencyObject
{
public string Name { get; set; } public int Age { get; set; }
} class School : DependencyObject
{
public static string GetGrade(DependencyObject obj)
{
return (string) obj.GetValue(GradeProperty);
} public static void SetGrade(DependencyObject obj, string value)
{
obj.SetValue(GradeProperty, value);
} // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GradeProperty =
DependencyProperty.RegisterAttached("Grade", typeof(string), typeof(School), new PropertyMetadata(""));
} class Company : DependencyObject
{
public static string GetPosition(DependencyObject obj)
{
return (string) obj.GetValue(PositionProperty);
} public static void SetPosition(DependencyObject obj, string value)
{
obj.SetValue(PositionProperty, value);
} // Using a DependencyProperty as the backing store for Position. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.RegisterAttached("PPosition", typeof(string), typeof(Company), new PropertyMetadata(""));
}

​ 使用依赖属性的方式如下:

            // Attached Properties
{
Human human0 = new Human() { Name = "John", Age = 10, };
School.SetGrade(human0, "四年级二班");
Human human1 = new Human() { Name = "Andy", Age = 26, };
Company.SetPosition(human1, "软件工程师");
Human human2 = new Human() { Name = "Kolity", Age = 25, };
Company.SetPosition(human2, "产品经理");
TextBoxAttached.Text += $"{human0.Name},{human0.Age},{School.GetGrade(human0)}\r\n";
TextBoxAttached.Text += $"{human1.Name},{human1.Age},{Company.GetPosition(human1)}\r\n";
TextBoxAttached.Text += $"{human2.Name},{human2.Age},{Company.GetPosition(human2)}\r\n";
}

​ 输出结果,如下所示:

John,10,四年级二班
Andy,26,软件工程师
Kolity,25,产品经理

​ 从附加属性的实现中,我们可以看出附加属性(Attached Properties)的本质就是依赖属性(Dependency Property)。附加属性通过声明与依赖属性相关的 Get 与 Set 方法实现寄宿在宿主类(例如:Human)上,这意味宿主类也必须实现 DependencyObject 类。

​ 其实,WPF 控件的布局控件的许多属性就为附加属性,例如:当把一个 TextBox 放入 Grid中时,对于 TextBox 而言我们可以使用 Grid 的 Row 和 Column 属性,如下:

        <Grid >
<TextBox Grid.Row="0" Grid.Column="0"></TextBox>
</Grid>

​ 放入 Canvas 中,可以使用 Canvas 的 Left 等附加属性:

        <Canvas>
<TextBox Canvas.Left="0" Canvas.Right="100" Canvas.Bottom="20" Canvas.Top="8"></TextBox>
</Canvas>

​ 附加属性(Attached Properties)的本质是依赖属性(Dependency Property),因此,附加属性也可以使用 Binding 依赖在其他对象的数据上,例如:我们通过两个 Slider 来控制矩形在 Canvas 中的横纵坐标:

  <Canvas x:Name="c1">
<Slider x:Name="s1" Width="200" Height="50" Canvas.Top="10" Canvas.Left="50" Maximum="300"></Slider>
<Slider x:Name="s2" Width="200" Height="50" Canvas.Top="40" Canvas.Left="50" Maximum="400"></Slider>
<Rectangle Fill="CadetBlue" Width="30" Height="30" Canvas.Left="{Binding ElementName=s1,Path=Value}" Canvas.Top="{Binding ElementName=s2,Path=Value}"></Rectangle>
</Canvas>

WPF 之 依赖属性与附加属性(五)的更多相关文章

  1. WPF的依赖属性和附加属性(用法解释较全)

    转:https://www.cnblogs.com/zhili/p/WPFDependencyProperty.html 一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己 ...

  2. WPF之依赖属性和附加属性

     参考资料: 一站式WPF--依赖属性(DependencyProperty)一 一站式WPF--依赖属性(DependencyProperty)二         依赖属性之我见: 这两篇文章介绍的 ...

  3. WPF依赖属性(续)(2)依赖属性与附加属性的区别

    原文:WPF依赖属性(续)(2)依赖属性与附加属性的区别        接上篇,感谢各位的评论,都是认为依赖属性的设计并不是为了节省内存,从大的方面而讲是如此.样式,数据绑定,动画样样都离不开它.这篇 ...

  4. [转]WPF的依赖属性是怎么节约内存的

    WPF升级了CLR的属性系统,加入了依赖属性和附加属性.依赖属性的使用有很多好处,其中有两点是我认为最为亮眼的: 1)节省内存的开销; 2)属性值可以通过Binding依赖于其它对象上,这就使得我的数 ...

  5. WPF的依赖属性

    Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR)属性的功能,这些服务通常统称为 WPF 属性系统.由 WPF 属 ...

  6. XAML实例教程系列 - 依赖属性和附加属性(四)

    XAML实例教程系列 - 依赖属性和附加属性 2012-06-07 13:11 by jv9, 1479 阅读, 5 评论, 收藏, 编辑 微软发布Visual Studio 2012 RC和Wind ...

  7. wpf 的依赖属性只能在loaded 事件之后才能取到

    wpf 的依赖属性只能在loaded 事件之后才能取到,在构造函数的  InitializeComponent(); 之后取不到 wpf 的依赖属性只能在loaded 事件之后才能取到,在构造函数的  ...

  8. WPF 中依赖属性的继承(Inherits)

    WPF中依赖属性的值是是可以设置为可继承(Inherits)的,这种模式下,父节点的依赖属性会将其值传递给子节点.例如,数据绑定中经常使用的DataContextProperty: var host ...

  9. WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性

    原文:WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性 如果你要自定义一个图片按钮控件,那么如何在主窗体绑定这个控件上图片的Source呢? ...

随机推荐

  1. filleSystemBasises

    基本查询命令 pwd 查看当前目录 dir 显示当前目录下的文件信息 more 查看文本文件的具体内容 cd 修改用户当前目录 mkdir 创建新的目录 rmdir 删除目录 copy filenam ...

  2. 【Azure Developer】使用Postman获取Azure AD中注册应用程序的授权Token,及为Azure REST API设置Authorization

    Azure Active Directory (Azure AD) is Microsoft's cloud-based identity and access management service, ...

  3. 实验一-最小生成树Kruskal算法

    实验名称 最小生成树算法-Kruskal算法 实验目的 1.掌握并查集的合并优化和查询优化: 2.掌握Kruskal算法. 3.能够针对实际问题,能够正确选择贪心策略. 4.能够针对选择的贪心策略,证 ...

  4. 剑指offer之重建二叉树

    1.问题描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.        例如输入前序遍历序列pre {1,2,4,7,3,5,6, ...

  5. 深入理解nodejs中的异步编程

    目录 简介 同步异步和阻塞非阻塞 javascript中的回调 回调函数的错误处理 回调地狱 ES6中的Promise 什么是Promise Promise的特点 Promise的优点 Promise ...

  6. STM32F207时钟系统解析

    在前几天的文章<晶振原理解析>中介绍了晶振如何产生时钟的,板子使用的是25M无源晶振,下文将介绍STM32F207的时钟系统如何将25M晶振时钟转换为120M系统主频时钟的. 01.时钟系 ...

  7. luoguP2016 战略游戏

    题目描述 Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题.他要建立一个古城堡,城堡中的路形成一棵树.他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了 ...

  8. linux自定义安装位置安装jdk

    注:本文系参考网络内容及本人实践得出 1 下载jdk安装包 下载地址:https://www.oracle.com/java/technologies/javase/javase-jdk8-downl ...

  9. 中文电子病历命名实体识别(CNER)研究进展

    中文电子病历命名实体识别(CNER)研究进展 中文电子病历命名实体识别(Chinese Clinical Named Entity Recognition, Chinese-CNER)任务目标是从给定 ...

  10. # Set the asyncio reactor's event loop as global # TODO: Should we instead pass the global one into the reactor?

    daphne/server.py at master · django/daphne https://github.com/django/daphne/blob/master/daphne/serve ...