Reputation: 35430
When developing your own UserControl that has a collection type property, ObservableCollection<T>
appears to be a good fit because it notifies the control about item-level changes. However, the underlying ViewModel property is of type List<T>
and although ObservableCollection<T>
can be constructed using a List<T>
, I'm not sure how two-way binding will work in that scenario.
I checked out ItemsControl.ItemSource
for reference, but it looks like MS-guys did not consider ObservableCollection
for their purpose and instead live on the poor man's IEnumerable
. I'm not sure how ItemsControl
supports item-level changes since IEnumerable
doesn't provide such notifications.
So should I use IEnumerable
, List<T>
or ObservableCollection<T>
for my collection property? And how should I go about two-way binding and collection change notifications?
My VM contains a property of type List<Point>
named Points
(Point
is a complex model-level type, but for our purpose u could consider it to be X,Y coordinates). VM reads all points from the model at startup and then exposes them to the View layer through Points
property.
My UserControl displays these points and allows user to add new points or remove existing. Since UserControls normally do not bind to VM directly and instead provide public properties that Views can then bind to the underlying VM's properties, I have added a property named MyPoints
in the control. Upon MouseDown
in the UserControl, I update MyPoints
accordingly, but then need to reflect these changes in the underlying VM too.
Upvotes: 0
Views: 394
Reputation: 499
Ideally, you'd want you VM to have the ObservableCollection
- this can tell the view every time it needs to update. Assuming you have the ability to modify the VM, I'd recommend this. If your VM does not include and ObservableCollection
, you will not be able to receive item-level updates.
To enable two-way binding you'll need to use a dependency property in your View (only dependency properties (and specially designed classes) can accept bindings)
In your view (assuming it's called 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;
}
}
With this, you can now do:
<ns:MyControl MyPoints="{Binding vmPointsCollection}" />
This code won't work as is, but should serve as a guide to implementing the necessary features you require to provide binding.
As a side note, ItemsControl.ItemsSource
works with ItemsControl.Items
together to produce the overall effect, Items is an ItemCollecton
which has a CollectionChanged
event. When you set ItemsSource
, Items
is told about this, starts watching for changes in your ItemsSource
and reports them through its CollectionChanged
event.
Edit: A better implementation of this control would have an IEnumerable as the type of the MyPoints collection and, when this is found in the handler, check if it implements INotifyCollectionChanged and attach handlers appropriately
Upvotes: 1