WPF附加属性无法为集合指定更改的回调

时间:2019-07-15 09:37:59

标签: c# wpf xaml attached-properties

编辑: 为了消除所有因即时关闭而造成的混乱。请参阅第(3.)点,解释为什么不接受接受的答案。简而言之,只要您不使用XAML设置值,链接的答案就可以了,因为XAML将从不调用PropertyChangedCallback,因为它重复使用了默认实例。


问题:
考虑具有XAML定义值的ObservableCollection<T>类型的简单WPF的附加属性

// public static class MyCollectionExetension.cs
public static ObservableCollection<int> GetMyCollection(DependencyObject obj)
{
    return (ObservableCollection<int>)obj.GetValue(MyCollectionProperty);
}

public static void SetMyCollection(DependencyObject obj, ObservableCollection<int> value)
{
    obj.SetValue(MyCollectionProperty, value);
}

public static readonly DependencyProperty MyCollectionProperty =
    DependencyProperty.RegisterAttached("MyCollection", typeof(ObservableCollection<int>), 
    typeof(MyCollectionExetension), new PropertyMetadata(null);

public static void DoThisWhenMyCollectionChanged(DependencyObejct assignee, IEnumerable<int> newValues) {
   // how can I invoke this?
}

//UserControl.xaml
<Grid xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <b:DataGridExtensions.MyCollection >
        <sys:Int32>1</sys:Int32>
        <sys:Int32>2</sys:Int32>
    </b:DataGridExtensions.MyCollection>
</Grid>

如何通过同时访问其附加的DependencyObject和新项目来挂钩更改集合的事件? MyCollection 必须必须在XAML中定义。
乍一看似乎很简单,但以下任何一项对我都不起作用:

  1. 设置回调new UIPropertyMetadata(null, CollectionChanged)会导致崩溃:
  

XamlObjectWriterException:'集合属性'System.Windows.Controls.Grid'。'MyCollection'为空。'

  1. 好的,让我们提供默认值以避免上面的崩溃:new UIPropertyMetadata(new ObservableCollection<int>(), CollectionChanged) 但这会阻止CollectionChanged触发,因为XAML不会实例化新集合,而是将项目添加到现有集合中。

  2. 修复以上问题并提供默认值CollectionChanged的同时钩住new UIPropertyMetadata(ProvideWithRegisteredCollectionChanged(), CollectionChanged)也不起作用,原因是由于无法将DependencyProperty传递给ProvideWithRegisteredCollectionChanged()方法处于静态环境中。

  3. MyCollection的getter或GetMyCollection()中的CoerceValueCallback中进行联合销售并不能阻止上述第1点的崩溃,因为在首次访问属性之前似乎没有调用它。

1 个答案:

答案 0 :(得分:1)

您不能为集合类型附加属性正确分配一个非空的默认值。因此,您必须在XAML中创建一个实例。

由于在XAML中直接声明ObservableCollection似乎不太容易,因此请声明适当的派生类型:

public class MyCollection : ObservableCollection<int>
{
}

并在XAML中创建一个实例,如下所示:

<Grid>
    <b:MyCollectionExtension.MyCollection>
        <b:MyCollection>
            <sys:Int32>1</sys:Int32>
            <sys:Int32>2</sys:Int32>
        </b:MyCollection>
    </b:MyCollectionExtension.MyCollection>
</Grid>

附加的属性声明应如下图所示,包括附加和分离CollectionChanged事件处理程序的代码。

public static class MyCollectionExtension
{
    public static MyCollection GetMyCollection(DependencyObject obj)
    {
        return (MyCollection)obj.GetValue(MyCollectionProperty);
    }

    public static void SetMyCollection(DependencyObject obj, MyCollection value)
    {
        obj.SetValue(MyCollectionProperty, value);
    }

    public static readonly DependencyProperty MyCollectionProperty =
        DependencyProperty.RegisterAttached(
            "MyCollection",
            typeof(MyCollection),
            typeof(MyCollectionExtension),
            new PropertyMetadata(MyCollectionPropertyChanged));

    public static void MyCollectionPropertyChanged(
        DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var oldCollection = e.OldValue as MyCollection;
        var newCollection = e.NewValue as MyCollection;

        if (oldCollection != null)
        {
            oldCollection.CollectionChanged -= MyCollectionChanged;
        }
        if (newCollection != null)
        {
            newCollection.CollectionChanged += MyCollectionChanged;
        }
    }

    public static void MyCollectionChanged(
        object o, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            // ...
        }
    }
}