Question

I made a wrapper class around SortedList. I add objects to this class expecting them to be automatically sorted alphabetically, but when I bind to a ListBox in WPF, I see then in unsorted order.

public class SortedObservableCollection<T> : ICollection<T>, INotifyPropertyChanged, INotifyCollectionChanged where T : INotifyPropertyChanged//, IComparable<T>
{
    private readonly SortedList<string,T> _innerSortedList;

    public SortedObservableCollection()
    {
        _innerSortedList = new SortedList<string, T>();
    }

    public void Add(T item)
    {
        _innerSortedList.Add(item.ToString(), item);
        this.OnPropertyChanged("Count");
        this.OnPropertyChanged("Item[]");
        this.OnCollectionChanged(NotifyCollectionChangedAction.Add, item);
        item.PropertyChanged += ItemPropertyChanged;
    }

    void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public void Clear()
    {
        _innerSortedList.Clear();
    }

    public bool Contains(T item)
    {
        return _innerSortedList.ContainsKey(item.ToString());
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public int Count
    {
        get { return _innerSortedList.Count; }
    }

    public bool IsReadOnly
    {
        get { throw new NotImplementedException(); }
    }

    public bool Remove(T item)
    {
        bool success = _innerSortedList.Remove(item.ToString());
        if (success)
        {
            item.PropertyChanged -= ItemPropertyChanged;
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
            this.OnCollectionChanged(NotifyCollectionChangedAction.Remove, item);
        }
        return success;
    }

    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _innerSortedList.GetEnumerator();
    }

    protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action, object item)
    {
        if (this.CollectionChanged != null)
        {
            this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public event NotifyCollectionChangedEventHandler CollectionChanged;
}

To bind I simply do :

SortedObservableCollection<IRCUser> Users { get; private set; }
.. fill users...
lstUsers.ItemsSource = users;

Sample input :

5Muhammad0
2Muhammad1
5Muhammad2

The output also shows similar, with the ones beginning with 2, 4 etc riddled between the 5's.

Note: My IRCUser class did implement IComparable, but since I only want an alphabetical sort now I commented the implentation out hoping the default sorting would kick in.

NOTE 2: I have override the toString() method, which I forgot to mention :

public override string ToString()
{
    return (int)Type + Nick;
}

UPDATE :

It seems internally the sortedList maintains the right order, but it is not passed to the ListBox in the right order...

Was it helpful?

Solution

You are not correctly create the event object NotifyCollectionChangedEventArgs. This object has different overloads of constructor depending on the action. You must use the overload that uses the index of the new item when you create a new item:

new NotifyCollectionChangedEventArgs(action, item, index)

Here's quote from MSDN:

Initializes a new instance of the NotifyCollectionChangedEventArgs class that describes an Add or Remove change.

NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction, Object, Int32)

UPDATE 0

Also it is better not to use an overload of method ToString to compare items, and use the special IComparer<TKey> for this. In your case, the correct code looks like this:

public void Add(T item)
{
    var key = item.ToString();
    _innerSortedList.Add(key, item);
    this.OnPropertyChanged("Count");
    this.OnPropertyChanged("Item[]");
    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _innerSortedList.IndexOfKey(key)));
    item.PropertyChanged += ItemPropertyChanged;
}

public bool Remove(T item)
{
    var indexOfKey = _innerSortedList.IndexOfKey(item.ToString());
    if (indexOfKey == -1)
        return false;
    _innerSortedList.RemoveAt(indexOfKey);
    item.PropertyChanged -= ItemPropertyChanged;
    this.OnPropertyChanged("Count");
    this.OnPropertyChanged("Item[]");
    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item,
        indexOfKey));
    return true;
}

public IEnumerator<T> GetEnumerator()
{
    return _innerSortedList.Values.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
    var handler = this.CollectionChanged;
    if (handler != null)
    {
        handler(this, args);
    }
}

OTHER TIPS

The problem is with

_innerSortedList.Add(item.ToString(), item);

Let suppose if item is of type Project.Test.CustomEntity then item.ToString() will give you "Project.Test.CustomEntity" which is getting sorted by Entity's fullname and not by the value.

you need to write a custom sorter based on property selector of your T.

Have a look at this article.

I'm not sure but:

1) If the UI has a data template, and the sample output you showed there is not the result of IRCUser.ToString() - than, maybe, the ToString() does not provide a "good" hash value for sorting.

2) You may be better served by using a PagedCollectionView to wrap your collection, and set the ordering there.

You are sorting your data on item.ToString() which may not be a very useful value to sort on.

Unless it is overridden, it will be the type name of the class and therefore the same for everything you add.


This obviously leads to the question, how should generic data be sorted. This is what IComparable<T> is for.


You'll note that there are several SortedList<T> constructors that allow you to pass an IComparable implementation to define your own order.

In any case, if you want to use the default IComparable implementation of string, you will need to use strings that differ in accordance with your desired order. Not type names that do not differ at all.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top