3

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.

Community
  • 1
  • 1
Kjara
  • 1,750
  • 8
  • 28
  • Possible duplicate of [how to sort ObservableCollection](http://stackoverflow.com/questions/7284805/how-to-sort-observablecollection) – ASh Oct 13 '16 at 11:16
  • you could override the InsertItem method – Wouter Oct 13 '16 at 21:31

3 Answers3

6

You should implement INotifyCollectionChanged Interface instead.

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

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

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

    public void RemoveAndNotify(T element)
    {
        Remove(element);
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
    }
}
ivayle
  • 1,060
  • 9
  • 16
  • This looks promising. Didn't know about INotifyCollectionChanged before, thanks! But when I call `RemoveAndNotify`, I get a runtime error. It says that the constructor for `NotifyCollectionChangedEventArgs` only supports the action `Reset`, whatever that means... – Kjara Oct 13 '16 at 11:56
  • MSDN says that `new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, element)` is valid (see here: https://msdn.microsoft.com/en-us/library/ms653207(v=vs.110).aspx ). But I still get a runtime error saying something about an index... What?? – Kjara Oct 13 '16 at 12:07
  • @Kjara Does it say anything in particular about an index? I don't think there's a SomethingUnspecifiedAboutAnIndexException. – 15ee8f99-57ff-4f92-890c-b56153 Oct 13 '16 at 12:56
  • @EdPlunkett Sorry. It is an InvalidOperationException saying: "Vom 'Remove'-Ereignis für eine Auflistung muss eine Elementposition angegeben werden." (my translation: "Event 'Remove' requires specifying an element index.") Source of this exception is PresentationFramework. StackTrace starts with MS.Internal.Data.EnumerableCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args), followed by the self-implemented Remove method from above. – Kjara Oct 13 '16 at 13:05
  • 1
    Seems that even though MSDN allows a lot of constructors (https://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedeventargs(v=vs.110).aspx), only one or two are actually valid for 'Remove' (like this one: https://msdn.microsoft.com/en-us/library/ms653212(v=vs.110).aspx) Which now leads to question http://stackoverflow.com/q/6255414/5333340 because `SortedSet` does not have indices. – Kjara Oct 13 '16 at 13:07
  • @Kjara Ohhh, gotcha. Would that KeyedCollection solution work for you? I wonder if you could add "fake" indexing to your class, too. – 15ee8f99-57ff-4f92-890c-b56153 Oct 13 '16 at 13:12
  • @EdPlunkett You mean according to http://stackoverflow.com/a/6284385/5333340 ? If I can't find any other way, I might try it... – Kjara Oct 13 '16 at 13:19
3

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)!

Community
  • 1
  • 1
Kjara
  • 1,750
  • 8
  • 28
  • I'm having exactly this problem and will use your workaround. `EnumerableCollectionView` has some problems with it not accepting what appears to be perfectly valid args. – NWoodsman Oct 24 '17 at 00:35
0

You can use a CollectionViewSource:

<Window.Resources>
    <CollectionViewSource x:Key="yourViewSource" source="{Binding YourCollection}"/>
</Window.Resources>

Then, sort it by the property you need:

CollectionViewSource yourViewSource=
            ((CollectionViewSource)(this.FindResource("yourViewSource")));
yourViewSource.SortDescriptions.Add(new SortDescription("SortByProperty", ListSortDirection.Descending));

Bind it as your ItemSource:

ItemsSource="{Binding Source={StaticResource yourViewSource}}"
Rom
  • 1,122
  • 1
  • 8
  • 18
  • No I can't. `CollectionViewSource` does not support hierarchical data - or if it does, I have not found a way yet to utilize it (and StackOverflow has a LOT of questions about this!). – Kjara Oct 13 '16 at 11:29