在WPF中,(自定义)依赖属性和附加属性有什么不同?每一个都有什么用处?这些实现通常有什么不同?
发布于 2018-05-09 16:07:16
摘要
因为我几乎没有找到关于这个问题的文档,所以我在source code上花了一些时间,但这是一个答案。
将依赖属性注册为常规属性和附加属性之间存在差异,而不是“哲学”属性(常规属性用于声明类型及其派生类型,附加属性用于任意DependencyObject
实例的扩展)。“哲学”,因为,正如@MarqueIV在他对@ReedCopsey的回答的评论中注意到的那样,常规属性也可以用于任意的DependencyObject
实例。
此外,我不得不不同意其他关于附加属性是“依赖属性的类型”的回答,因为它具有误导性--没有任何依赖属性的“类型”。框架并不关心属性是否被注册为附加的--它甚至不可能确定(从这个意义上说,这个信息没有被记录,因为它是不相关的)。实际上,所有属性都被注册为附加属性,但对于常规属性,需要做一些额外的操作来稍微修改它们的行为。
代码摘录
为了省去您自己翻阅源代码的麻烦,下面是所发生的事情的精简版本。
注册未指定元数据的属性时,调用
DependencyProperty.Register(
name: "MyProperty",
propertyType: typeof(object),
ownerType: typeof(MyClass))
产生与调用完全相同的结果
DependencyProperty.RegisterAttached(
name: "MyProperty",
propertyType: typeof(object),
ownerType: typeof(MyClass))
但是,在指定元数据时,调用
DependencyProperty.Register(
name: "MyProperty",
propertyType: typeof(object),
ownerType: typeof(MyClass),
typeMetadata: new FrameworkPropertyMetadata
{
CoerceValueCallback = CoerceCallback,
DefaultValue = "default value",
PropertyChangedCallback = ChangedCallback
});
等同于调用
var property = DependencyProperty.RegisterAttached(
name: "MyProperty",
propertyType: typeof(object),
ownerType: typeof(MyClass),
defaultMetadata: new PropertyMetadata
{
DefaultValue = "default value",
});
property.OverrideMetadata(
forType: typeof(MyClass),
typeMetadata: new FrameworkPropertyMetadata
{
CoerceValueCallback = CoerceCallback,
DefaultValue = "default value",
PropertyChangedCallback = ChangedCallback
});
结论
常规依赖项属性和附加依赖项属性之间的关键(也是唯一的)区别是通过DependencyProperty.DefaultMetadata属性提供的默认元数据。这甚至在一节中也提到过:
对于非附加属性,不能将此属性返回的元数据类型强制转换为PropertyMetadata类型的派生类型,即使该属性最初注册为派生元数据类型也是如此。如果您希望原始注册的元数据包括其原始的可能派生的元数据类型,请改为调用GetMetadata(Type),并将原始注册类型作为参数传递。
对于附加属性,此属性返回的元数据的类型将与原始RegisterAttached注册方法中给定的类型相匹配。
这在所提供的代码中是清晰可见的。注册方法中也隐藏了一些小提示,例如,对于RegisterAttached
,元数据参数被命名为defaultMetadata
,而对于Register
,它被命名为typeMetadata
。对于附加属性,提供的元数据将成为默认元数据。但是,对于常规属性,默认元数据始终是仅设置了DefaultValue
的PropertyMetadata
的新实例(来自提供的元数据或自动设置)。只有对OverrideMetadata
的后续调用才实际使用所提供的元数据。
后果
主要的实际区别在于,在常规属性的情况下,CoerceValueCallback
和PropertyChangedCallback
仅适用于从声明为所有者类型的类型派生的类型,而对于附加属性,它们适用于所有类型。例如,在此场景中:
var d = new DependencyObject();
d.SetValue(SomeClass.SomeProperty, "some value");
如果属性被注册为附加属性,则注册的PropertyChangedCallback
将被称为,但如果它被注册为常规属性,则将不被称为。CoerceValueCallback
也是如此。
第二个不同之处在于OverrideMetadata
要求所提供的类型派生自DependencyObject
。实际上,这意味着常规属性的所有者类型必须从DependencyObject
派生,而中的附加属性可以是任何类型(包括静态类、结构、枚举、委托等)。
补充
除了@MarqueIV的建议之外,我还在几个场合遇到过这样的观点,即常规属性和附加属性在XAML中的使用方式不同。也就是说,常规属性需要隐式名称语法,而不是附加属性所需的显式名称语法。从技术上讲,这不是真的,尽管在实践中通常是这样的。为了清楚起见:
<!-- Implicit property name -->
<ns:SomeClass SomeProperty="some value" />
<!-- Explicit property name -->
<DependencyObject ns:SomeClass.SomeProperty="some value" />
在纯XAML中,控制这些语法使用的唯一规则如下:
>F257
满足这些条件使您可以使用相应的语法,而不管支持依赖项属性是注册为常规还是附加。
现在所提到的误解是由于绝大多数教程(连同现有的Visual Studio代码片段)指导您对常规依赖属性使用CLR属性,并为附加的属性获取/设置访问器。但是,没有什么可以阻止您同时使用这两种语法,允许您使用您喜欢的任何语法。
发布于 2009-08-06 10:56:17
附加属性是一种依赖属性。不同之处在于如何使用它们。
通过附加属性,该属性被定义在与使用它的类不同的类上。这通常用于布局。很好的例子是Panel.ZIndex或Grid.Row -你将其应用于一个控件(即:按钮),但它实际上是在面板或网格中定义的。该属性被“附加”到按钮的实例上。
例如,这允许容器创建可在任何UIelement上使用的属性。
至于实现的区别--这基本上就是在定义属性时使用注册和RegisterAttached的问题。
发布于 2012-01-10 11:33:57
附加属性基本上是指容器elements.like如果你有一个网格,你现在有一个grid.row这被认为是一个网格element.also的附加属性你可以在文本框,按钮等中使用这个属性来设置它在网格中的位置。
依赖属性类似于属性基本上属于某个其他类,并在其他类中使用。就像你在这里有一个矩形一样,height和width是rectangle的常规属性,但是left和top是依赖属性,因为它属于Canvass类。
https://stackoverflow.com/questions/1240699
复制相似问题