Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace Entire ObservableCollection with another ObservableCollection

public class Alpha
{
    public ObservableCollection<Beta> Items { get; set; }

    public Alpha()
    {
        Items = new ObservableCollection<Beta>();
    }

    public void DoSomething()
    {
        Items = GetNewItems();  // whenever I do this, Items gets a new referene, 
                                // so every WPF binding (e.g. datagrids) are broken
    }

    public ObservableCollection<Beta> GetNewItems()
    {
         var ret = new ObservableCollection<Beta>();
         // some logic for getting some items from somewhere, and populating ret
         return ret;
    }
}

How can I replace the whole content of Items with the return value of GetNewItems() without:

  1. Breaking the bindings.

  2. Having to loop through the items and copy them one by one to the other collection ?

like image 493
Adam Szabo Avatar asked Aug 01 '13 14:08

Adam Szabo


3 Answers

You have some options:

  1. Implement INotifyPropertyChanged so you can inform the UI that the value of Items has changed. This does not utilize the fact that INotifyCollectionChanged is implemented on ObservableCollection. It will work, but it defeats the purpose of using an ObservableCollection in the first place. This is not recommended but it works.
    • Example
  2. Use the ObservableCollection's Add/Remove/Modify/Update methods to modify it in conjunction with the Dispatcher.
    • Note: without the Dispatcher, you will get a NotSupportedException because CollectionViews do not support changes to their SourceCollection from a thread different from the Dispatcher thread.
  3. Use the ObservableCollection's Add/Remove/Modify/Update methods to modify it in conjunction with BindingOperations.EnableCollectionSynchronization. Recommended
    • Note: This is only available in .NET 4.5.
    • This is an alternative to using the Dispatcher while avoiding the NotSupportedException.
    • Example

Numbers 2 and 3, with respect to your question, translate to clearing the existing items (Clear()) and then adding (Add()) the items returned by whatever method you want - see the example for #3. They key is that the clearing and all of the adding must be done with Dispatcher (2) or by calling BindingOperations.EnableCollectionSynchronization. Good luck!

Reference: Reed Copsey Answer - StackOverflow

like image 163
Tyler Morrow Avatar answered Nov 01 '22 21:11

Tyler Morrow


ObservableCollection implements INotifyCollectionChanged, which will update bindings whenever items get added or removed. All that is needed here is to clear the list for the CollectionChanged event to fire.

public void GetNewItems()
{
     Items.Clear();

     // some logic for getting some items from somewhere, and populating ret

}
like image 37
James Sampica Avatar answered Nov 01 '22 22:11

James Sampica


You can also make your own class that will extend the ObservableCollection, here is an example with notifications sorted:

https://github.com/jamesmontemagno/mvvm-helpers/blob/master/MvvmHelpers/ObservableRangeCollection.cs

I'm using simpler implementation of above (I did not compared the notification aspect yet):

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using BaseLibrary.Properties;

namespace BaseLibrary
{
/// <summary> 
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
    //INotifyPropertyChanged interited from ObservableCollection<T>
    #region INotifyPropertyChanged

    protected override event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged

    /// <summary> 
    /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
    /// </summary> 
    public void AddRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException(nameof(collection));

        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
    /// </summary> 
    public void RemoveRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException(nameof(collection));

        foreach (var i in collection) Items.Remove(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Clears the current collection and replaces it with the specified item. 
    /// </summary> 
    public void Replace(T item)
    {
        Replace(new T[] { item });
    }

    /// <summary> 
    /// Replaces all elements in existing collection with specified collection of the ObservableCollection(Of T). 
    /// </summary> 
    public void Replace(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException(nameof(collection));

        Items.Clear();
        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
    /// </summary> 
    public ObservableCollectionEx()
        : base() { }

    /// <summary> 
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
    /// </summary> 
    /// <param name="collection">collection: The collection from which the elements are copied.</param> 
    /// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception> 
    public ObservableCollectionEx(IEnumerable<T> collection)
        : base(collection) { }
}

}

like image 30
Jakub Pawlinski Avatar answered Nov 01 '22 20:11

Jakub Pawlinski