【问题标题】:What's the difference between a dependency property and an attached property in WPF?WPF中的依赖属性和附加属性有什么区别?
【发布时间】:2009-08-06 18:52:12
【问题描述】:

(自定义)依赖属性和 WPF 中的附加属性有什么区别?各有什么用途?这些实现通常有何不同?

【问题讨论】:

    标签: .net wpf dependency-properties attached-properties


    【解决方案1】:

    附加属性是一种依赖属性。不同之处在于它们的使用方式。

    对于附加属性,该属性是在一个与其所使用的类不同的类上定义的。这通常用于布局。很好的例子是 Panel.ZIndex 或 Grid.Row - 您将其应用于控件(即:Button),但它实际上是在 Panel 或 Grid 中定义的。该属性“附加”到按钮的实例。

    例如,这允许容器创建可用于任何 UI 元素的属性。

    至于实现差异 - 基本上只是在定义属性时使用 Register 与 RegisterAttached 的问题。

    【讨论】:

    • 但到底有什么区别?!从我所见,您可以通过代码将不可附加的属性附加到另一个属性(我认为这在 XAML 中被阻止。)也许这就是区别?
    【解决方案2】:

    摘要

    由于我几乎没有找到关于此事的文档,因此我花了一些时间在 source code 上搜索,但这里有一个答案。

    将依赖属性注册为常规属性和附加属性之间存在区别,而不是“哲学”属性(常规属性旨在由声明类型及其派生类型、附加属性使用旨在用作任意 DependencyObject instances 的扩展。 “哲学”,因为正如@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 属性获得的默认元数据。 Remarks 部分甚至提到了这一点:

    对于非附加属性,此属性返回的元数据类型不能转换为PropertyMetadata 类型的派生类型,即使该属性最初是使用派生元数据类型注册的。如果您希望原始注册的元数据包括其原始可能派生的元数据类型,请改为调用GetMetadata(Type),将原始注册类型作为参数传递。

    对于附加属性,该属性返回的元数据的类型将匹配原始RegisterAttached注册方法中给出的类型。

    这在提供的代码中清晰可见。注册方法中也隐藏了一些小提示,即对于RegisterAttached,元数据参数被命名为defaultMetadata,而对于Register,它被命名为typeMetadata。对于附加属性,提供的元数据成为默认元数据。但是,在常规属性的情况下,默认元数据始终是 PropertyMetadata 的新实例,仅设置了 DefaultValue(来自提供的元数据或自动设置)。只有随后对OverrideMetadata 的调用才会真正使用提供的元数据。

    后果

    主要的实际区别在于,在常规属性的情况下,CoerceValueCallbackPropertyChangedCallback适用于从声明为所有者类型的类型派生的类型,对于附加属性,它们'重新适用于所有类型。例如。在这种情况下:

    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 中,管理这些语法使用的唯一规则如下:

    • 隐式名称语法可以用于元素当且仅当该元素表示的类具有该名称的 CLR 属性
    • 可以在元素上使用显式名称语法当且仅当全名的第一部分指定的类公开了适当的静态get/set 方法(称为accessors) 名称与全名的第二部分匹配

    满足这些条件使您可以使用相应的语法,而不管支持的依赖属性是注册为常规还是附加。

    现在提到的误解是由于绝大多数教程(连同股票 Visual Studio 代码 sn-ps)指示您使用 CLR 属性进行常规依赖属性,以及附加属性的获取/设置访问器。但是没有什么能阻止你同时使用这两种语法,让你可以使用你喜欢的任何语法。

    【讨论】:

    • 这是我读过的最好的 SO 答案之一。如果你写一本书,我就买。
    • 您确定“甚至无法确定(在此信息未记录的意义上)”。 ?我想知道,因为我刚刚发现了MarkupProperty.IsAttachedDependencyPropertyDescriptor.IsAttached。你有关于这些的任何信息吗?
    • @nalka 我以前从未见过这些。我不确定MarkupProperty 类是用来做什么的,所以我现在无法给你一个明确的答案。我的第一印象是它代表了将对象序列化为标记(XAML?)时使用的某种元数据,包括用于编写特定属性的语法(附加/非附加)。但是我不确定它是否 100% 准确。我什至不确定如何定义非附加属性 - 如果您实际上没有调用 Register 方法,而是自己做所有事情,您会认为这样的属性是非附加的吗?
    【解决方案3】:

    附加属性基本上是用于容器元素的。比如如果你有一个网格并且你有 grid.row 现在这被认为是一个网格元素的附加属性。你也可以在 texbox、按钮等中使用这个属性设置它在网格中的位置。

    依赖属性就像属性基本上属于某个其他类并在其他类中使用。 例如:就像你有一个矩形 这里 height 和 width 是矩形的常规属性,而 left 和 top 是依赖属性,因为它属于 Canvass 类。

    【讨论】:

      【解决方案4】:

      附加属性是一种特殊的 DependencyProperties。它们允许您将值附加到对该值一无所知的对象。 这个概念的一个很好的例子是布局面板。每个布局面板都需要不同的数据来对齐其子元素。 Canvas 需要 Top 和 Left,DockPanel 需要 Dock 等。由于您可以编写自己的布局面板,因此列表是无限的。所以你看,不可能在所有 WPF 控件上都拥有所有这些属性。 解决方案是附加属性。它们由在特定上下文中需要来自另一个控件的数据的控件定义。例如由父布局面板对齐的元素。

      【讨论】:

        【解决方案5】:

        我认为您可以在类本身中定义附加属性,也可以在另一个类中定义它。我们总是可以使用附加属性来扩展标准的微软控件。但是依赖属性,你在你自己的自定义控件中定义它。例如您可以从标准控件继承您的控件,并在您自己的控件中定义一个依赖属性并使用它。这相当于定义一个附加属性,并在标准控件中使用这个附加属性。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-20
          • 2011-05-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-15
          • 1970-01-01
          • 2020-12-22
          相关资源
          最近更新 更多