WPF - 属性系统 - APaas(AttachedProperty as a service)
是的,文章的题目看起来很牛,我承认。
附加属性是WPF中的一个非常重要的功能。例如在设置布局的过程中,软件开发人员就常常通过DockPanel的Dock附加属性来设置其各个子元素所处的布局位置。同样地,在为程序添加一个新的功能时,我们也常常需要创建自定义的附加属性来完成该功能。
附加属性简介
首先,我们要对附加属性有一个简单的认识:什么是附加属性,而为什么WPF提供了附加属性呢?
在WPF中,附加属性用来表示定义在一个类型上,却可以在其它特定类型实例上被使用的属性。由于该属性并非定义在这些实例所对应的类型之上,而更像是为这些类型额外地添加了一系列值,因此其被称为附加属性。
一种较好的理解附加属性这种行为的方式则是将其当作一个服务。其中子元素通过设置这些附加属性的值来定义如何使用服务。而定义附加属性的类型将根据这些子元素中设置的数值决定其所提供服务的方式,如一个控件应该放置在哪里。这种使用方法也便是决定我们是否创建一个附加属性的最重要因素。
而实现一个附加项属性则非常简单。首先,软件开发人员需要通过调用DependencyProperty类的RegisterAttached()函数在属性系统中声明一个附加属性。与依赖项属性不同的是,由于其并非作用于定义该附加属性的类型上,因此无法提供CLR属性包装,而是提供了以Get-以及Set-作为属性名前缀的专用方法。这些专用方法中,软件开发人员可以通过规定参数以及返回值的类型等方式限制使用该附加属性的类型以及该属性的值。
现在就以.net源码中的DockPanel.Dock附加属性的实现来熟悉创建附加属性的过程:
public static readonly DependencyProperty DockProperty = DependencyProperty
.RegisterAttached("Dock", typeof(Dock), typeof(DockPanel),
……); public static Dock GetDock(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (Dock) element.GetValue(DockProperty);
} public static void SetDock(UIElement element, Dock dock)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(DockProperty, dock);
}
首先,软件开发人员需要通过DependencyProperty类的RegisterAttached()以及RegisterAttachedReadonly()函数来向属性系统中注册附加属性。这两个函数所使用的各个参数与Register()函数所使用的各个参数类似:通过参数name指定附加属性的名称;通过propertyType参数指定附加属性的类型;通过ownerType参数指定附加属性所在的类型;通过defaultMetadata参数指定附加属性所使用的元数据;最后通过validateValueCallback指定验证逻辑。而在附加属性的承载类型中,软件开发人员需要通过一个静态公有的DependencyProperty来记录这些新注册的附加属性。这个附加属性的属性名需要遵守一定的规则:其需要由在调用RegisterAttached()函数时所传入的参数name以及后缀-Property组合而成。接下来,软件开发人员就需要定义用来访问附加属性的读写函数。这些函数的名称则需要通过前缀Get-和Set-以及附加属性注册时所传入的参数name共同组成。在这些函数内部,软件开发人员只需要调用DependencyObject的GetValue()和SetValue()即可。
让我们来回想一下WPF中DockPanel的使用过程:软件开发人员首先在XAML中声明了一个DockPanel,并在该其中声明了众多的子元素,以表示需要承载在该DockPanel中的界面组成。在这些界面组成的声明中,我们可以使用DockPanel.Dock属性来指定每个子元素需要放置在DockPanel的哪个位置。整个过程如下面代码所示:
<DockPanel LastChildFill="True">
<Border DockPanel.Dock="Top" ...>
<TextBlock Foreground="Black">Dock = "Top"</TextBlock>
</Border>
...
</DockPanel>
在XAML编译器遇到对DockPanel.Dock属性的赋值时,其会将该赋值转化为对DockPanel.SetDock()函数的调用。该函数会以包含DockPanel.Dock属性赋值的界面元素以及被赋予的值作为参数。而在执行布局计算的时候,DockPanel会使用GetDock()函数将所有子元素的DockPanel.Dock附加属性值读取出来,并根据这些值安排各个子元素所处的位置。这就相当于DockPanel提供布局计算的服务,而每个子元素则通过附加属性DockPanel.Dock提供了自身所需要的服务的类型。
另外需要强调的一点则是附加属性的Get-和Set-函数中对能使用附加属性的类型的限制。在DockPanel所提供的GetDock()和SetDock()函数中,第一个属性的类型是UIElement,也就表示DockPanel只为UIElement提供附加属性的服务。这其实也是附加属性限制其所可施行类型的最常用方法:在Get-和Set-函数的定义中将第一个参数的类型设置为附加属性的目标类型,从而限制其它类型对该附加属性的使用。
同理,软件开发人员也可以为Get-函数的返回值以及Set-函数的参数value指定一个特定的类型,以防止用户代码为附加属性设置一个其它类型的值。其内部实现会直接调用传入的参数的GetValue()和SetValue()函数来完成附加属性的设置。这两个函数并没有以自身实例作为参数。也就是说,附加属性并没有设置在声明该附加属性的类型实例上,而是设置在了使用附加属性的实例上。
从上面的讲解中可以看出,附加属性所对应的Get-和Set-函数内部并没有引用任何宿主类型成员,而是将属性值设置到了传入的参数上。因此在定义一个附加属性的时候,依赖项属性的宿主类型不必从DependencyObject类派生。
如果需要让附加属性所对应的服务能够正确执行,仅仅限制附加属性的目标类型是不够的。附加属性所提供的服务常常需要按照一定的方式搜索其所在类型之下的各个子元素,以搜集它们所包含的有关服务的信息,并根据这些信息提供服务。就以DockPanel类所提供的DockPanel.Dock附加属性为例:
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElementCollection internalChildren = base.InternalChildren;
int count = internalChildren.Count;
for (int i = ; i < count; i++)
{
UIElement element = internalChildren[i];
if (element != null)
{
Size desiredSize = element.DesiredSize;
Rect finalRect = … // 子元素最终所处的布局位置
switch (GetDock(element))
{
case Dock.Left:
x += desiredSize.Width;
finalRect.Width = desiredSize.Width;
break; case Dock.Top: // 依次处理Dock.Top,Dock.Right以及Dock.Bottom等情况
}
element.Arrange(finalRect);
}
}
return arrangeSize;
}
上面的示例代码非常好地展示了一个附加属性是如何在类型中被使用的:在通常情况下,包含附加属性的类型常常包含了一个集合,以记录其所包含的各个子元素。在特定逻辑运行过程中,该集合内所记录的所有子元素将被遍历。在每次遍历中,执行逻辑都会取得子元素的附加属性值,并以此属性值来决定子元素的具体行为。
因此,如果这些信息并没有按照正确的方式提供,那么附加属性所提供的服务也可能不被正确运行。就以下面的代码为例:
<DockPanel LastChildFill="True">
<Border DockPanel.Dock="Top" ...>
<TextBlock DockPanel.Dock=”Bottom” Foreground="Black">Dock = "Top"</TextBlock>
</Border>
...
</DockPanel>
在上面的代码中,DockPanel的Border类型的子元素以及Border所包含的TextBlock元素都设置了DockPanel.Dock附加属性。但是由于DockPanel仅仅排列其所包含的各个子元素,而这些子元素所各自包含的内嵌元素的布局则是由各个子元素自己决定,因此DockPanel仅仅对它的直接子元素的DockPanel.Dock附加属性进行处理。所以在上面的例子中,TextBlock元素所设置的DockPanel.Dock附加属性将不会被处理。
附加属性相关特性
现在来想想附加属性在XAML中的使用。在编写XAML标记的时候,Visual Studio会根据当前输入提示可用的各属性。这其中就有附加属性:
但是这里就出现了问题:该列表中并没有DockPanel.Dock附加属性,却只有Grid相关的附加属性。这是为什么呢?查看有关附加属性的实现时,我们发现了一些端倪:
[AttachedPropertyBrowsableForChildren]
public static int GetColumn(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (int) element.GetValue(ColumnProperty);
}
在上面的代码中,我们可以看到Column附加属性的Get-访问函数上添加了一个特性AttachedPropertyBrowsableForChildren。查看MSDN可以知道,该属性用来标示当前附加属性可以在逻辑树中的子元素中被使用。其包含两种不同的子元素查询方法:在没有显式地将IncludeDescendants属性标为true的时候,依赖项属性将仅仅在子元素中可见。而在将IncludeDescendants属性标为true的时候,该依赖项属性可以在相应元素中的任何子元素中出现。一般情况下,该特性只在Get-访问符上施行。
在定义一个附加属性的时候,我们的确可以通过标示这些特性使得一个附加属性可以在一个元素的属性列表中出现。但是该附加属性的设置是否被处理则是由附加属性的实际执行逻辑所决定的。因此软件开发人员在使用该特性使一个附加属性在某个元素中出现的时候,您首先需要尽量保证处理逻辑能处理该特性所标示的附加属性可见范围。
另一个较为有用的特性就是AttachedPropertyBrowsableForType。该特性允许软件开发人员指定一个附加属性只对特定类型可见。在需要令附加属性对多种类型可见的时候,软件开发人员需要在附加属性的Get-访问符上添加多个该类型的特性。
而最后一个特性就是AttachedPropertyBrowsableWhenAttributePresentAttribute。该特性表示只有宿主类型上标明了特定特性时,该依赖项属性才可见。这并不是一个非常常用的特性,因此我们将不在这里对其进行讲解。
附加属性的绑定
从上面的讲解中您已经了解到,附加属性实际上就是一个依赖项属性,因此其同样可以作为绑定的数据源使用。但是此时绑定所使用的标记则与普通的依赖项属性有所不同。例如在需要绑定到静态实例local:StaticClass的Button属性所设置的Grid.Row附加属性的时候,软件开发人员需要使用圆括号将附加属性括起:
{Binding Source={x:Static local:StaticClass}, Path=Button.(Grid.Row)}
使用不同标记的原因则非常简单:XAML解析器需要根据不同的标记来判断绑定表达式中的各个组成到底是对依赖项属性还是附加属性的引用。
转载请注明原文地址:http://www.cnblogs.com/loveis715/p/4343381.html
商业转载请事先与我联系:silverfox715@sina.com,我只会要求添加作者名称以及博客首页链接。
WPF - 属性系统 - APaas(AttachedProperty as a service)的更多相关文章
- WPF - 属性系统 (4 of 4)
依赖项属性的重写 在基于C#的编程中,对属性的重写常常是一种行之有效的解决方案:在基类所提供的属性访问符实现不能满足当前要求的时候,我们就需要重新定义属性的访问符. 但对于依赖项属性而言,属性执行逻辑 ...
- WPF - 属性系统 (3 of 4)
依赖项属性元数据 在前面的章节中,我们已经介绍了WPF依赖项属性元数据中的两个组成:CoerceValueCallback回调以及PropertyChangedCallback.而在本节中,我们将对其 ...
- WPF - 属性系统 (2 of 4)
属性更改回调 前一章的示例中,对各个参数的设置都非常容易理解.如果我们仅仅需要创建一个独立的依赖项属性,那么上面所提到的创建依赖项属性的基础知识足以满足需求.但是事情往往并非如此完美.在一个系统中,很 ...
- WPF - 属性系统 (1 of 4)
本来我希望这一系列文章能够深入讲解WPF属性系统的实现以及XAML编译器是如何使用这些依赖项属性的,并在最后分析WPF属性系统的实际实现代码.但是在编写的过程中发现对WPF属性系统代码的讲解要求之前的 ...
- WPF 属性系统 依赖属性之内存占用分析
关于WPF的属性系统园子内有不少这方面的文章.里面大都提到了WPF依赖属性的在内存方面的优化.但是里面大都一笔带过.那么WPF到底是怎么样节约内存的.我们通过WPF属性和普通的CLR属性对比来看一下W ...
- wpf控件开发基础(3) -属性系统(2)
原文:wpf控件开发基础(3) -属性系统(2) 上篇说明了属性存在的一系列问题. 属性默认值,可以保证属性的有效性. 属性验证有效性,可以对输入的属性进行校验 属性强制回调, 即不管属性有无发生变化 ...
- Android 属性系统 Property service 设定分析 (转载)
转自:http://blog.csdn.net/andyhuabing/article/details/7381879 Android 属性系统 Property service 设定分析 在Wind ...
- wpf控件开发基础(4) -属性系统(3)
原文:wpf控件开发基础(4) -属性系统(3) 知识回顾 接上篇,上篇我们真正接触到了依赖属性的用法,以及依赖属性的属性元数据的用法,并且也实实在在地解决了之前第二篇提到的一系列问题.来回顾一下 属 ...
- wpf控件开发基础(2) -属性系统(1)
原文:wpf控件开发基础(2) -属性系统(1) 距离上篇写的时间有1年多了.wpf太大,写的东西实在太多,我将依然围绕着自定义控件来展开与其相关的技术点. 也欢迎大家参与讨论.这篇我们将要讨论的是W ...
随机推荐
- Entity Framework Core 1.1 升级通告
原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1/ 翻译:杨晓东 ...
- “.Net 社区虚拟大会”(dotnetConf) 2016 Day 2 Keynote: Miguel de Icaza
美国时间 6月7日--9日,为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开,美国时间6.8 是第二天, Miguel de Icaza 做Keynote,Miguel 在波士顿Xa ...
- JSON.parse()和JSON.stringify()
1.parse 用于从一个字符串中解析出json 对象.例如 var str='{"name":"cpf","age":"23&q ...
- css3中perspective
perspective 属性定义 3D 元素距视图的距离,以像素计.该属性允许改变 3D 元素查看 3D 元素的视图.当为元素定义 perspective 属性时,其子元素会获得透视效果,而不是元素本 ...
- Canvas绘图之平移translate、旋转rotate、缩放scale
画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transform()来改变,它们会对画布的变换矩阵产生影响. 函数 方法 描 ...
- javascript数组查重方法总结
文章参考地址:http://blog.csdn.net/chengxuyuan20100425/article/details/8497277 题目 对下列数组去重: var arr = ['aa', ...
- 获取 dhcp IP 过程分析 - 每天5分钟玩转 OpenStack(91)
前面我们已经讨论了 DHCP agent 的配置以及 namespace 如何隔离 dnsmasq 服务,本节将以 cirros-vm1 为例分析获取 DHCP IP 的详细过程. 在创建 insta ...
- nodejs操作arduino入门(javascript操作底层硬件)
用Javascript来操作硬件早就不是一件稀奇的事情了. 所以作为一名电子专业出身的FE,我也打算尝试一下用js来驱动arduino: 要想操作这些底层硬件,肯定是需要一些工具的,我这里介绍的工具主 ...
- C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent
看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex S ...
- 集合(set)-Python3
set 的 remove() 和 discard() 方法介绍. 函数/方法名 等价操作符 说明 所有集合类型 len(s) 集合基数:集合s中元素个数 set([obj]) 可变集合工 ...