dotNET
dotNET

Reputation: 35430

Standard way of implementing collection type property in a UserControl

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?

Edit

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

Answers (1)

Richard SP.
Richard SP.

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

Related Questions