Search code examples
c#wpfdata-binding

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.
}

Solution

  • 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.