Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Replacing databound collection contents without Clear/Add

When using WPF databinding, I obviously can't do something along the lines of MyCollection = new CollectionType<Whatever>( WhateverQuery() ); since the bindings have a reference to the old collection. My workaround so far has been MyCollection.Clear(); followed by a foreach doing MyCollection.Add(item); - which is pretty bad for both performance and aesthetics.

ICollectionView, although pretty neat, doesn't solve the problem either since it's SourceCollection property is read-only; bummer, since that would have been a nice and easy solution.

How are other people handling this problem? It should be mentioned that I'm doing MVVM and thus can't rummage through individual controls bindings. I suppose I could make a wrapper around ObservableCollection sporting a ReplaceSourceCollection() method, but before going that route I'd like to know if there's some other best practice.

EDIT:

For WinForms, I would bind controls against a BindingSource, allowing me to simply update it's DataSource property and call the ResetBindings() method - presto, underlying collection efficiently changed. I would have expected WPF databinding to support a similar scenario out of the box?

Example (pseudo-ish) code: WPF control (ListBox, DataGrid, whatever you fancy) is bound to the Users property. I realize that collections should be read-only to avoid the problems demonstrated by ReloadUsersBad(), but then the bad code for this example obviously wouldn't compile :)

public class UserEditorViewModel
{
    public ObservableCollection<UserViewModel> Users { get; set; }

    public IEnumerable<UserViewModel> LoadUsersFromWhateverSource() { /* ... */ }

    public void ReloadUsersBad()
    {
        // bad: the collection is updated, but the WPF control is bound to the old reference.
        Users = new ObservableCollection<User>( LoadUsersFromWhateverSource() );
    }

    public void ReloadUsersWorksButIsInefficient()
    {
        // works: collection object is kept, and items are replaced; inefficient, though.
        Users.Clear();
        foreach(var user in LoadUsersFromWhateverSource())
            Users.Add(user);
    }

    // ...whatever other stuff.
}
like image 723
snemarch Avatar asked Nov 29 '10 10:11

snemarch


2 Answers

If the object MyCollection is of implements INotifyPropertyChanged, you can simply replace the collection.

An example:

public class MyClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private ObservableCollection<Whatever> _myCollection;

    private void NotifyChanged(string property)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public ObservableCollection<Whatever> MyCollection
    {
        get
        {
            return _myCollection;
        }
        set
        {
            if (!ReferenceEquals(_myCollection, value))
            {
                _myCollection = value;
                NotifyChanged("MyCollection");
            }
        }
    }
}

With this, when you assign a collection, WPF detects this and everything gets updated.

This is how I'd solve this.

like image 125
Pieter van Ginkel Avatar answered Nov 01 '22 06:11

Pieter van Ginkel


The link below explains how to implement an AddRange method.

http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

It looks like you're stuck with implementing a sub-class that handles this case correctly.

Apparently, certain controls don't support batched collection change notifications. At least they didn't when that article was written. Though now you should have a bit more information if you want to investigate further.

like image 31
Josh Smeaton Avatar answered Nov 01 '22 05:11

Josh Smeaton