WPF中的(自定义)依赖项属性和附加属性之间有什么区别?每种用途有什么用?通常,实现方式有何不同?
Answers:
由于我几乎没有找到有关此问题的文档,因此花了一些时间在源代码周围,但这是一个答案。
将依赖项属性注册为常规属性和附加属性(“哲学”属性除外)(将常规属性用于声明类型及其派生类型,将附加属性用作任意 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
。对于附加属性,提供的元数据将成为默认元数据。但是,在使用常规属性的情况下,默认元数据始终是PropertyMetadata
带有DefaultValue
设置的新实例(通过提供的元数据或自动生成)。只有随后的调用才能OverrideMetadata
实际使用提供的元数据。
主要的实际区别是,在常规性的情况下,CoerceValueCallback
并PropertyChangedCallback
适用只有从声明为所有者类型类型派生类型,以及附加属性它们适用于所有类型。例如在这种情况下:
var d = new DependencyObject();
d.SetValue(SomeClass.SomeProperty, "some value");
注册PropertyChangedCallback
将被调用,如果财产被注册为附加属性,但不会被调用,如果它被注册为普通的属性。相同CoerceValueCallback
。
次要的差异源于OverrideMetadata
要求提供的类型源自的事实DependencyObject
。实际上,这意味着常规属性的所有者类型必须从DependencyObject
,而in中的附加属性可以是任何类型(包括静态类,结构,枚举,委托等)。
除了@MarqueIV的建议外,我还多次遇到这样的观点,即常规属性和附加属性在XAML中的使用方式不同。即,常规属性需要隐式名称语法,而不是附加属性所需的显式名称语法。从技术上讲这是不正确的,尽管在实践中通常是这种情况。为了清楚起见:
<!-- Implicit property name -->
<ns:SomeClass SomeProperty="some value" />
<!-- Explicit property name -->
<DependencyObject ns:SomeClass.SomeProperty="some value" />
在纯XAML中,管理这些语法用法的唯一规则如下:
满足这些条件使您可以使用相应的语法,而不管支持依赖项属性是注册为常规还是附加的。
现在,提到的误解是由以下事实造成的:绝大多数教程(连同库存的Visual Studio代码段)都指示您将CLR属性用于常规依赖项属性,并为附加的依赖项获取/设置访问器。但是没有什么可以阻止您同时使用两者,从而允许您使用自己喜欢的语法。
附加属性是一种依赖项属性。不同之处在于它们的使用方式。
使用附加属性,可以在与使用该属性的类不同的类上定义该属性。通常用于布局。很好的例子是Panel.ZIndex或Grid.Row-您将其应用于控件(即:按钮),但实际上是在Panel或Grid中定义的。该属性“附加”到按钮的实例。
例如,这允许容器创建可在任何UIelement上使用的属性。
至于实现上的差异-定义属性时,基本上只需要使用Register与RegisterAttached。