WPF 之 依赖属性与附加属性(五)
一、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 之 依赖属性与附加属性(五)的更多相关文章
- WPF的依赖属性和附加属性(用法解释较全)
转:https://www.cnblogs.com/zhili/p/WPFDependencyProperty.html 一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己 ...
- WPF之依赖属性和附加属性
参考资料: 一站式WPF--依赖属性(DependencyProperty)一 一站式WPF--依赖属性(DependencyProperty)二 依赖属性之我见: 这两篇文章介绍的 ...
- WPF依赖属性(续)(2)依赖属性与附加属性的区别
原文:WPF依赖属性(续)(2)依赖属性与附加属性的区别 接上篇,感谢各位的评论,都是认为依赖属性的设计并不是为了节省内存,从大的方面而讲是如此.样式,数据绑定,动画样样都离不开它.这篇 ...
- [转]WPF的依赖属性是怎么节约内存的
WPF升级了CLR的属性系统,加入了依赖属性和附加属性.依赖属性的使用有很多好处,其中有两点是我认为最为亮眼的: 1)节省内存的开销; 2)属性值可以通过Binding依赖于其它对象上,这就使得我的数 ...
- WPF的依赖属性
Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR)属性的功能,这些服务通常统称为 WPF 属性系统.由 WPF 属 ...
- XAML实例教程系列 - 依赖属性和附加属性(四)
XAML实例教程系列 - 依赖属性和附加属性 2012-06-07 13:11 by jv9, 1479 阅读, 5 评论, 收藏, 编辑 微软发布Visual Studio 2012 RC和Wind ...
- wpf 的依赖属性只能在loaded 事件之后才能取到
wpf 的依赖属性只能在loaded 事件之后才能取到,在构造函数的 InitializeComponent(); 之后取不到 wpf 的依赖属性只能在loaded 事件之后才能取到,在构造函数的 ...
- WPF 中依赖属性的继承(Inherits)
WPF中依赖属性的值是是可以设置为可继承(Inherits)的,这种模式下,父节点的依赖属性会将其值传递给子节点.例如,数据绑定中经常使用的DataContextProperty: var host ...
- WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性
原文:WPF 使用依赖属性(DependencyProperty) 定义用户控件中的Image Source属性 如果你要自定义一个图片按钮控件,那么如何在主窗体绑定这个控件上图片的Source呢? ...
随机推荐
- 第12章 DOM操作
目录 *1. 向DOM中注入HTML 1.1 将HTNL字符串转换成DOM 预处理HTML源字符串 包装HTML 1.2 将DOM元素插入到文档中 2. DOM的特性和属性 通过DOM方法和属性访问特 ...
- 大数相加Java
题目 以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回. 分析 两个字符串,定义两个指针,分别从这两个字符串的结尾开始遍历,因为可能字符串1比字符串2长度要长,因此只要两者其中有 ...
- Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类
目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...
- JAR冲突问题的解决以及运行状态下如何查看加载的类
今天碰到群里小伙伴问,线上程序好像有多个不同版本的Netty包,怎么去看到底加载了哪一个? 在说如何看之前,先来说说,当你开始意识到项目里有多个不同版本的Jar包,都是因为遇到了这几个异常: java ...
- mysql:如何解决数据修改冲突(事务+行级锁的实际运用)
摘要:最近做一个接诊需求遇到一个问题,假设一个订单咨询超过3次就不能再接诊,但如果两个医生同时对该订单进行咨询,查数据库的时候查到的接诊次数都是2次,那两个医生都能接诊,所谓接诊可以理解为更新了接诊次 ...
- 03--Docker 容器和镜像常用命令
一.帮助命令 docker version docker info docker --help =====================镜像命令=========================== ...
- IP2188中文资料书
IP2188 是一款集成 12 种.用于 USB 输出端口的快充协议 IC,支持 USB 端口充电协议.支持 11种快充协议,包括 USB TypeC PD2.0/PD3.0/PPS DFP,HVDC ...
- RecyclerView 源码分析(二) —— 缓存机制
在前一篇文章 RecyclerView 源码分析(一) -- 绘制流程解析 介绍了 RecyclerView 的绘制流程,RecyclerView 通过将绘制流程从 View 中抽取出来,放到 Lay ...
- Android 代码规范大全
前言 虽然我们项目的代码时间并不长,也没经过太多人手,但代码的规范性依然堪忧,目前存在较多的比较自由的「代码规范」,这非常不利于项目的维护,代码可读性也不够高, 此外,客户端和后端的研发模式也完全不同 ...
- Python+Selenium+Unittest实现PO模式web自动化框架(4)
1.PageLocators目录下的具体模块 2.PageLocators目录下主要放置个页面的元素定位.用于统一管理个页面的定位元素. 例如:登录页面的元素定位login_page_locator. ...