在 UserControl 中实现 collection 类型 属性 的标准方法

Standard way of implementing collection type property in a UserControl

在开发自己的具有 collection 类型 属性 的 UserControl 时,ObservableCollection<T> 似乎是一个很好的选择,因为它会通知控件有关 item-level 的更改。但是,底层 ViewModel 属性 是 List<T> 类型,虽然 ObservableCollection<T> 可以使用 List<T> 构造,但我不确定 two-way 绑定如何工作在那种情况下。

我参考了ItemsControl.ItemSource,但看起来MS-guys并没有考虑ObservableCollection的目的,而是靠穷人的IEnumerable生活。我不确定 ItemsControl 如何支持 item-level 更改,因为 IEnumerable 不提供此类通知。

那么我应该为 collection 属性 使用 IEnumerableList<T> 还是 ObservableCollection<T>?我应该如何处理 two-way 绑定和 collection 更改通知?

编辑

我的 VM 包含一个名为 PointsList<Point> 类型的 属性(Point 是一个复杂的 model-level 类型,但出于我们的目的,您可以考虑它是 X,Y 坐标)。 VM在启动时从模型中读取所有点,然后通过Points属性暴露给View层。

My UserControl 显示这些点并允许用户添加新点或删除现有点。由于 UserControls 通常不直接绑定到 VM,而是提供 public 属性,然后 Views 可以绑定到底层 VM 的属性,因此我在控件中添加了一个名为 MyPoints 的 属性。在 UserControl 中 MouseDown 后,我相应地更新 MyPoints,但随后也需要在底层 VM 中反映这些更改。

理想情况下,您希望 VM 具有 ObservableCollection - 这可以在每次需要更新时告诉视图。假设您有能力修改 VM,我会推荐这个。如果您的 VM 不包含 ObservableCollection,您将无法接收商品级更新。

要启用双向绑定,您需要在视图中使用依赖项 属性(只有依赖项属性(以及专门设计的 类)可以接受绑定)

在您看来(假设它叫做 MyControl ):

// Provide a dependency property to enable binding
public static readonly DependencyProperty MyPointsProperty = DependencyProperty.Register( "MyPoints", typeof(ObservableCollection<Points>) ,new FrameworkPropertyMetadata( null, FrameworkPopertyMetatdataOptions.None, MyPointsPropertyChangedHandler ) );

// Provide a CLR property for convenience - this will not get called by the binding engine!
public ObservableCollection<Points> MyPoints {
    get { return (ObservableCollection<Points>) GetValue( MyPointsProperty ); } 
    set { SetValue( MyPointsProperty, value ); }
}

//Listen for changes to the dependency property (note this is a static method)
public static void MyPointsPropertyChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e) {
    MyControl me = obj as MyControl;
    if( me != null ) {
        // Call a non-static method on the object whose property has changed
        me.OnMyPointsChanged( (ObservableCollection<Points>) e.OldValue, (ObservableCollection<Points>) e.NewValue );
    }
}
// ...

// Listen for changes to the property (non-static) to register CollectionChanged handlers
protected virtual void OnMyPointsChanged(ObservableCollection<Points> oldValue, ObservableCollection<Points> newValue) {
    if(oldValue!=null){
        oldValue.CollectionChanged -= MyCollectionChangedHandler;
    }
    if( newValue!=null ) {
        newValue.CollectionChanged += MyCollectionChangedHandler;
    }
}

有了这个,你现在可以做到:

<ns:MyControl MyPoints="{Binding vmPointsCollection}" />

此代码不会按原样工作,但应作为指南,以实现提供绑定所需的必要功能。

作为旁注,ItemsControl.ItemsSourceItemsControl.Items 一起产生整体效果,Items 是一个 ItemCollecton,它有一个 CollectionChanged事件。当您设置 ItemsSource 时,Items 会获知此事,开始监视您的 ItemsSource 中的变化并通过其 CollectionChanged 事件报告它们。

Edit:此控件的更好实现是将 IEnumerable 作为 MyPoints 集合的类型,当在处理程序中找到它时,检查它是否实现 INotifyCollectionChanged 和适当地附加处理程序