一、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. 利用dotnet-dump分析docker容器内存泄露

    目录 一 运行官方示例 1,Clone代码并编译 2,创建Dockerfile构建镜像 3,启动容器 二 生成dump转储文件 1,制造问题 2,创建dump文件 三 分析dump文件 1,创建一个用 ...

  2. C++ 简单输出当前日期时间

    根据https://www.runoob.com/cplusplus/cpp-date-time.html编写. 首先介绍2个数据类型. 一个是time_t,与时间函数相关的变量,定义的变量记录着自 ...

  3. 【二分搜索树】1、二分查找法的实现 - Binary Search

    简单记录 - bobo老师的玩转算法系列–玩转算法 - 二分搜索树 二叉搜索树 Binary Search Tree 查找问题 Searching Problem 查找问题是计算机中非常重要的基础问题 ...

  4. 超过varchar定义长度

    mysql> select version();+------------+| version() |+------------+| 5.1.73-log |+------------+1 ro ...

  5. 【排序基础】1、选择排序法 - Selection Sort

    文章目录 选择排序法 - Selection Sort 为什么要学习O(n^2)的排序算法? 选择排序算法思想 操作:选择排序代码实现 选择排序法 - Selection Sort 简单记录-bobo ...

  6. 【MYSQL】win7安装mysql-5.7.10绿色版

    1.下载 :mysql下载地址 2.解压缩 3.环境变量配置 MYSQL_HOME=D:\mysql-5.7.11-win32 PATH=%MYSQL_HOME%\bin 4.修改配置文件 a.)将m ...

  7. 【Linux】fdisk -l发现有加号"+"

    今天分区不够了,打算扩下分区,想起当时创建机器的时候还有大约80多个G没有用,今天打算重新利用上 就用了fdisk /dev/sda 创建了两个分区,但是发现分区下面有加号,感到而别的困惑 最后在很多 ...

  8. HTTP协议相关知识整理:

    http协议简介 超文本传输协议:是一种用于分布式.协作式和超媒体信息系统的应用层协议. 一次请求一次响应之后断开连接(无状态,短连接) 分析http请求信息格式 http工作原理 以下是 HTTP ...

  9. jackson学习之四:WRAP_ROOT_VALUE(root对象)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  10. 阿里云ECS hadoop+spark+zookeeper+hive code-server 集群搭建

    懒得重新排版然后发到博客了.用在线文档看吧 https://www.kdocs.cn/l/srV6o8rABW9V 用线上IDE(code-server)写scala的时候,出现BUG可以参考下面两篇 ...