一、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. Spring用了哪些设计模式?

    设计模式是一套被反复使用的.多数人知晓的.经过分类编目的.代码设计经验的总结.总共有 23 种设计模式 使用设计模式是为了重用代码.让代码更容易被他人理解.保证代码可靠性. Spring用了哪些设计模 ...

  2. vue的路由组件挂载。

    vue通过多种方式可以将组件挂载到一个页面上.挂载方式有四种.其实也并不止四种.这里呢就简单的提四种方式去怎样挂载组件. 第一种就是作为标签形式挂载.前面也提到. 后面的就是一般的挂载组件和按需挂载组 ...

  3. 牺牲速度来节省内存,Redis是觉得自己太快了吗

    前言 正常情况下我们选择使用 Redis 就是为了提升查询速度,然而让人意外的是,Redis 当中却有一种比较有意思的数据结构,这种数据结构通过牺牲部分读写速度来达到节省内存的目的,这就是 zipli ...

  4. If you see someone without smile

    If you see someone without smile, give them one of yours. 难怪我每次和不认识的人说话都放肆大笑.

  5. Win 10 Docker安装和简单使用

    Win 10 Docker安装和简单使用 1.环境准备 Docker for Windows需要运行在64位Windows 10 Pro专业版.企业版或教育版(1607年纪念更新,版本14393或更高 ...

  6. g/test/s/lose/won/g

    包含字符串test的任意行商,用lose代替won

  7. 【Linux】CentOS4 系统最后的网络yum源

    ------------------------------------------------------------------------------------------------- | ...

  8. 【Oracle】B-tree和函数索引

    转自:https://www.cnblogs.com/yumiko/p/5957613.html 函数索引 1.1 概述 在实际应用中,当条件列使用函数运算进行数据匹配时,即使该列建立了索引,索引也不 ...

  9. JDBC入门程序总结

    JDBC本质 只是一个接口 每个数据库的规范 就是实现类的接口 其实是官方 定义的一套操作所有关系型数据库的规则,就是接口,各个数据库厂商去实现这套接口,提供数据库驱动jar包, 我们可以使用这套接口 ...

  10. kubernets之secret资源

    一  对于一些保密度比较高的文件,k8s又是如何存储的呢? 针对那些保密度比较高的配置文件,例如证书以及一些认证配置不能直接存储在configmap中,而是需要存储在另外一种资源中,需要对存储在里面的 ...