为什么我应该使用附加属性而不是常规依赖属性?

时间:2014-08-07 09:56:15

标签: wpf dependency-properties attached-properties

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

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)时才需要这样做。

3 个答案:

答案 0 :(得分:4)

关于您的示例,您说将常规依赖项属性附加到另一个对象。您的所有代码都是在string中存储Dictionary值以及对象的引用。这样做使其成为附加属性 - 重要的是,您无法直接从string访问Button值,因为Text属性没有Button {1}}。

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

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

现在回答你的问题:

  

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

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

更新&gt;&gt;&gt;

根据你的评论,你似乎拒绝接受我所概述的差异......你说:

  

除了我不能在XAML中使用它之外,最终用户的角度没有什么区别。

首先,你不认为这是一个巨大的差异吗?你无法将它用于数据绑定的开始。此外,您一直说您可以将属性附加到您尚未使用DependencyProperty声明的类型,但您100%不正确。您可以直接在代码 XAML中引用附加属性,而不能直接在XAML 代码中引用您调用附加属性的内容。< / p>

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

  

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

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

将值添加到Dictionary 属性设置机制。因此,您再次失去了在StyleAnimationTrigger等中使用假装附加财产的能力。

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

答案 1 :(得分:1)

如果仔细查看依赖项属性标识符,所有 DP都在类DependencyProperty 中注册,我们会在注册时传递Owner类的类型和属性名称。

样品:

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

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


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

首先,它将获得在注册属性时生成的唯一值,并将键值对添加到类Dependency Object中声明的名为 _effectiveValues 的私有字典中,并将Key设置为唯一的哈希码在注册时,价值是用户设定的价值。

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

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

不确定但是在XAML中只有在WPF人员强制执行类型检查的情况下可能存在约束。遗憾的是,在MSDN上没有关于此的书面文档。

答案 2 :(得分:0)

当您想要控制现有控件但不想扩展它时,会发现附加属性。一个很好的例子是,没有办法在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