【问题标题】:Why should I use an attached property instead of a regular dependency property?为什么我应该使用附加属性而不是常规依赖属性?
【发布时间】:2014-08-07 09:56:15
【问题描述】:

我刚刚发现我可以做到以下几点:

var button = new Button();
button.SetValue(TextBlock.TextProperty, "text");
var text = (string)button.GetValue(TextBlock.TextProperty); // text is "text" 

虽然上面的例子有点不切实际,但它确实表明我可以将常规依赖属性附加到另一个对象上。它不必是附加属性(TextBlock.TextProperty 未在 DependencyProperty.RegisterAttached() 注册。

这暴露了为什么首先会有附加属性的问题?我现在可以看到的唯一区别是我无法在 XAML 中附加常规依赖项属性。但仅此而已。还有其他区别吗?

更新:

为了更清楚起见,以下代码可以正常工作,并且从最终用户的角度来看非常接近附加属性:

public static class AttachedPropertyDeclarer
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        "Text",
        typeof(string),
        typeof(Button), 
        new PropertyMetadata(default(string),OnTextChanged));

    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // do something when text changed
    }
}

...
button.SetValue(AttachedPropertyDeclarer.TextProperty, "text");
var text = (string)button.GetValue(AttachedPropertyDeclarer.TextProperty);

将此与附加属性方式进行比较:

public static class AttachedPropertyDeclarer
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
        "Text",
        typeof(string),
        typeof(AttachedPropertyDeclarer),
        new PropertyMetadata(default(string),OnTextChanged));

    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // do something when text changed
    }
}

这里与附加属性的唯一有效区别是我必须声明类型为Button 的所有者,而在附加属性中它通常是AttachedPropertyDeclarer。但这仅在我需要更改事件处理程序(即OnTextChanged)时才需要完成。

【问题讨论】:

  • 示例中的代码有效,因为框架不限制基于属性注册类型的 SetValue 和 GetValue,即。附加的或常规的。其次,依赖对象是一个简单的属性容器,其中 DP 是键,值是您存储的内容。最后设计者不会允许这样的使用,因为存储的将没有任何用处,因为目标依赖对象没有使用存储值的知识/实现。
  • 为什么投反对票?如何改进问题?
  • 如@pushpraj 所述。例如,它们用于扩展现有框架控件的功能。您在创建自己的自定义控件时使用 DP,而附加的 DP 用于扩展功能。就我个人而言,我更喜欢使用行为,那就是在可能的情况下。看看:msdn.microsoft.com/en-us/library/ff725476(v=expression.40).aspx

标签: wpf dependency-properties attached-properties


【解决方案1】:

关于你的例子,你有 not 正如你所说,将一个常规的依赖属性附加到另一个对象上。您的所有代码都实现了将string 值与对您的对象的引用一起存储在Dictionary 中。这使其成为附加属性 - 重要的是,您不能直接从Button 访问该string 值,因为Text 上没有Text 属性Button

您的代码所做的实际上与此非常相似:

Dictionary<object, object> values2 = new Dictionary<object, object>();
var button = new Button();
values2.Add(button, "text");
string text = values2[button].ToString();

现在回答你的问题:

声明附加属性的主要原因是为了将属性添加到您未声明的类型,从而扩展其功能。

一个很好的例子是将SelectedItems 属性添加到ItemsControlListBox 类。在这样做时,我们扩展了类的当前或默认功能。另一个很好的例子是声明一个附加属性,它会自动将添加的项目带入视图(同样在 ItemsControlListBox 类中)。

更新>>>

根据您的 cmets,您似乎拒绝接受我所概述的差异...您说:

除了我不能在 XAML 中使用它之外,从最终用户的角度来看,实际上并没有什么区别。

首先,你不认为这是一个巨大的区别吗?..你一开始就不能用它来进行数据绑定。此外,您一直说您可以将属性附加到您尚未使用 DependencyProperty 声明的类型,但您是 100% 不正确的。您可以在代码 XAML 中直接引用附加属性,而不能在 XAML 代码中直接引用您调用的附加属性。

您所做的只是在Dictionary 中存储一个值,您当然不需要DependencyProperty 的开销来做到这一点。这样做与声明附加属性之间确实没有可比性。来自 MSDN 上的Attached Properties Overview 页面:

当有理由为定义类以外的类提供属性设置机制时,您可以创建附加属性。

注意以下部分:属性设置机制

将值添加到Dictionary不是一种属性设置机制。同样,您将无法在Styles、Animations、Triggers 等中使用您的假装附加属性。

为了一劳永逸地澄清这种情况,您可以开发一个简单的测试项目。为我提到的ListBox 实现IList SelectedItems 附加属性(您可以找到有关此的在线教程),然后使用您的假装附加属性(如果可能的话)执行相同操作。两者之间在开发简单性上的差异将清楚地向您展示为什么您应该使用附加属性而不是常规的DependencyProperty

【讨论】:

  • 我想当我使用附加属性时也会发生同样的事情,在这种情况下,它还会“在Dictionary 中存储string 值以及对我的对象的引用”。我仍然可以使用常规依赖属性“将属性添加到您未声明的类型”。在我看来,唯一真正的区别是我不能在 XAML 中使用它。
  • 我仍然可以使用常规依赖属性“将属性添加到您未声明的类型”...这就是重点...您 不能使用普通的DependencyProperty在我看来,唯一真正的区别是我不能在 XAML 中使用它...也不正确。您也不能在代码中使用 Button.Text 属性。您可以从Dictionary 中检索值,这不是根本
  • 在上面的代码中,我没有声明类型 Button,也没有声明 DependencyProperty TextBlock.Text,但我仍然将依赖属性 TextBlock.Text 的值添加到 Button 实例.
  • 您在Dictionary 中设置了一个值,但这并不意味着您创建或附加了任何内容。当然,您可以存储这样的值,但是您不能以任何方式使用 Button.Text 访问它,因此这样做没有任何好处,然后只使用免费的 Tag 属性...事实上,使用Tag 属性是一个更好的选择因为您可以直接在代码或 XAML 中访问它
  • 我已经更新了我的答案,使其更加清晰。除了我不能在 XAML 中使用它之外,从最终用户的角度来看,实际上并没有什么不同。
【解决方案2】:

如果您仔细查看依赖属性标识符,所有DP 都注册到类 DependencyProperty,并且我们在注册时传递 Owner 类类型和属性名称。 p>

示例:

public static readonly DependencyProperty IsSpinningProperty = 
    DependencyProperty.Register(
    "IsSpinning", typeof(Boolean), typeof(OwnerClass));

在注册时,它会创建一些独特的哈希码,结合属性名称和所有者类类型来唯一地表示每个 DP。


因此,当您在某个对象上为该 DP 设置值时,例如您在 Button 上的情况,代码流是这样的:

首先它将获取在注册属性时生成的唯一值,并将键值对添加到名为 _effectiveValues 在类依赖对象中声明的私有字典中,其中键设置为唯一的哈希码注册和值是用户设置的值。

注意 - MSDN 上没有这方面的书面文档,但通过使用反射器查看源代码来验证这一点。

所以,当你从后面的代码中设置值时,它会像我上面提到的那样工作,因为它不会在向字典中添加值之前验证它是否属于该类型,并且获取值将从字典中获取值。

不确定,但 XAML 中可能存在约束,仅在 WPF 人员强制执行类型检查的地方。遗憾的是,MSDN 上没有这方面的书面文档。

【讨论】:

    【解决方案3】:

    当您想要控制现有控件但不想扩展它时,会发现附加属性。一个很好的例子是,无法在 XAML 中为 WPF DatePicker 绑定 BlackOutDates 属性。在这种情况下,您可以使用附加属性来附加自定义功能以映射 BlackOutDates。这在 MVVM 中非常适合,因为附加属性提供了在 XAML 中绑定的方式。

    public class BlackOutDatesAdapter
    {
        public static List<DateTime> GetBlackOutDates(DependencyObject obj)
        {
            return (List<DateTime>)obj.GetValue(BlackOutDatesProperty);
        }
    
        public static void SetBlackOutDates(DependencyObject obj, List<DateTime> value)
        {
            obj.SetValue(BlackOutDatesProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for BlackOutDates.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BlackOutDatesProperty =
            DependencyProperty.RegisterAttached("BlackOutDates", typeof(List<DateTime>), typeof(BlackOutDatesAdapter), new PropertyMetadata(null, OnBlackOutDatesChanged));
    
        private static void OnBlackOutDatesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = sender as DatePicker;
            var list = (List<DateTime>)e.NewValue;
            foreach(var date in list)
            {
                control.BlackoutDates.Add(new CalendarDateRange(date));
            }
        }
    }
    

    XAML 中的绑定将如下所示,

    <DatePicker VerticalAlignment="Center"
            Width="200"
            local:BlackOutDatesAdapter.BlackOutDates="{Binding BlackOutDates}"
            DisplayDate="{Binding DisplayDate}" />
    

    在属性的回调中,您可以做自己的映射,将日期添加到 DatePicker。欲了解更多信息,请阅读此post

    【讨论】:

    • 我仍然可以使用常规依赖属性做同样的事情,只是不能在 XAML 中。所以区别只是 XAML 支持?
    • 是的,您可以在代码隐藏中执行所有操作。在上面的例子中,如果违反 MVVM 不是问题,那么我们不需要附加属性。但是在 MVVM 中,你不能用代码编写所有内容。这仍然不是附加财产的最终目的。一般来说,我们可以说它可以帮助您控制基于另一个对象的对象行为。像 Grid.Row 一样,grid 的子节点需要根据 Grid 内部的这个属性来对齐。你不能用依赖属性来做到这一点。
    猜你喜欢
    • 2011-01-20
    • 2015-08-25
    • 2018-06-10
    • 2010-10-11
    • 2010-12-15
    • 2011-02-04
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    相关资源
    最近更新 更多