在 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 属性 使用 IEnumerable
、List<T>
还是 ObservableCollection<T>
?我应该如何处理 two-way 绑定和 collection 更改通知?
编辑
我的 VM 包含一个名为 Points
的 List<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.ItemsSource
与 ItemsControl.Items
一起产生整体效果,Items 是一个 ItemCollecton
,它有一个 CollectionChanged
事件。当您设置 ItemsSource
时,Items
会获知此事,开始监视您的 ItemsSource
中的变化并通过其 CollectionChanged
事件报告它们。
Edit:此控件的更好实现是将 IEnumerable 作为 MyPoints 集合的类型,当在处理程序中找到它时,检查它是否实现 INotifyCollectionChanged 和适当地附加处理程序
在开发自己的具有 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 属性 使用 IEnumerable
、List<T>
还是 ObservableCollection<T>
?我应该如何处理 two-way 绑定和 collection 更改通知?
编辑
我的 VM 包含一个名为 Points
的 List<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.ItemsSource
与 ItemsControl.Items
一起产生整体效果,Items 是一个 ItemCollecton
,它有一个 CollectionChanged
事件。当您设置 ItemsSource
时,Items
会获知此事,开始监视您的 ItemsSource
中的变化并通过其 CollectionChanged
事件报告它们。
Edit:此控件的更好实现是将 IEnumerable 作为 MyPoints 集合的类型,当在处理程序中找到它时,检查它是否实现 INotifyCollectionChanged 和适当地附加处理程序