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

WPF Attached Property unable to specify Changed Callback for collection

编辑: 为了消除与即时关闭重复的所有混淆。请参阅第 (3.) 点,解释为什么已接受的答案不适用。简而言之,只要您不使用 XAML 设置值,链接的答案就可以了,因为 XAML 将 never 调用 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.
中定义 一开始看起来很简单,但是下面的 none 对我有用:

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

XamlObjectWriterException: 'Collection property 'System.Windows.Controls.Grid'.'MyCollection' is null.'

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

  2. 修复上述问题并在提供默认值 new UIPropertyMetadata(ProvideWithRegisteredCollectionChanged(), CollectionChanged) 的同时挂钩 CollectionChanged 也不起作用,因为无法将 DependencyProperty 传递给 ProvideWithRegisteredCollectionChanged() 方法,因为处于静态上下文中。

  3. GetMyCollection() getter 或 CoerceValueCallback 中合并 MyCollection 并不能防止上述第 1 点的崩溃,因为它似乎没有在 属性 最先访问。

您无法为附加的 collection-type 正确分配 non-null 默认值 属性。因此,您必须在 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)
        {
            // ...
        }
    }
}