Kjara Kjara - 1 month ago 9
C# Question

Implementing own ObservableCollection

For my WPF project I need an observable collection that always keeps the correct order. My idea is to use a

SortedSet<T>
and implement my own
AddAndNotify
and
RemoveAndNotify
methods. In them, I would call
NotifyPropertyChanged
, like this:

public class ObservableSortedSet<T> : SortedSet<T>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}

public void AddAndNotify(T element)
{
Add(element);
NotifyPropertyChanged(...);
}

public void RemoveAndNotify(T element)
{
Remove(element);
NotifyPropertyChanged(...);
}
}


But which property (or properties) would that be?

How can one implement a collection that tells the UI to update whenever the collection contents change?

Or is there an easier way by using the SortedSet directly in my ViewModel?

EDIT:

I don't want to use the predefined
ObservableCollection
together with a sorted view. I know this is possible by using either
CollectionViewSource
or converters, but these solutions don't appeal to me. I have hierarchichal data for which
CollectionViewSource
doesn't work, and I consider the converter version a horrible workaround for the limits of
CollectionViewSource
. I want to use a clean solution.

So this question is not a duplicate of how to sort ObservableCollection . I don't want to sort an
ObservableCollection
, I want to use a
SortedSet
that can tell changes to the UI.

Answer

I managed to do it this way:

public class ObservableSortedSet<T> : SortedSet<T>, INotifyCollectionChanged where T : IComparable<T>
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public new void Add(T element)
    {
        base.Add(element);
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public override void Clear()
    {
        base.Clear();
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new void Remove(T element)
    {
        base.Remove(element);
        CollectionChanged?.Invoke(this,
            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

Why did I always give the Reset action as EventArg?

  • Reason for Remove: It seems to be unable to be utilized if the collection does not have indices (see Implementing INotifyCollectionChanged on a collection without indexes or Observable Stack and Queue - in both cases it boils down to not being able to specify the correct index, resulting in a runtime error. Look at all the comments below the answers saying "index xyz doesn't work for me!".)

  • Reason for Add: Internally, the Collection IS sorted, but the UI does not reflect this correctly. My guess is that by giving the EventArg new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, element); the GUI only gets notified that the new element should be added TO THE END.

According to https://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(v=vs.100).aspx, Reset stands for "The content of the collection changed dramatically." This is kind of true for SortedSet, since its internal structure can change a lot if just one element is added or removed. So maybe my solution isn't even a workaround (even if it feels like one)!

Comments