I have a an ObservableCollection and a WPF UserControl is Databound to it. The Control is a graph that shows a vertical bar for each item of type BarData in the ObservableCo
I just created a class that extends the ObservableCollection
because over time I've also wanted other functionality that I'm used to using from a List
(Contains
, IndexOf
, AddRange
, RemoveRange
, etc)
I usually use it with something like
MyCollection.Sort(p => p.Name);
Here's my sort implementation
/// <summary>
/// Expanded ObservableCollection to include some List<T> Methods
/// </summary>
[Serializable]
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
/// <summary>
/// Constructors
/// </summary>
public ObservableCollectionEx() : base() { }
public ObservableCollectionEx(List<T> l) : base(l) { }
public ObservableCollectionEx(IEnumerable<T> l) : base(l) { }
#region Sorting
/// <summary>
/// Sorts the items of the collection in ascending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
public void Sort<TKey>(Func<T, TKey> keySelector)
{
InternalSort(Items.OrderBy(keySelector));
}
/// <summary>
/// Sorts the items of the collection in descending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
public void SortDescending<TKey>(Func<T, TKey> keySelector)
{
InternalSort(Items.OrderByDescending(keySelector));
}
/// <summary>
/// Sorts the items of the collection in ascending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
/// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
{
InternalSort(Items.OrderBy(keySelector, comparer));
}
/// <summary>
/// Moves the items of the collection so that their orders are the same as those of the items provided.
/// </summary>
/// <param name="sortedItems">An <see cref="IEnumerable{T}"/> to provide item orders.</param>
private void InternalSort(IEnumerable<T> sortedItems)
{
var sortedItemsList = sortedItems.ToList();
foreach (var item in sortedItemsList)
{
Move(IndexOf(item), sortedItemsList.IndexOf(item));
}
}
#endregion // Sorting
}
What about sorting the data using LINQ on the different collection:
var collection = new List<BarData>();
//add few BarData objects to collection
// sort the data using LINQ
var sorted = from item in collection orderby item.StartData select item;
// create observable collection
var oc = new ObservableCollection<BarData>(sorted);
This worked for me.
hummm first question I have for you is:
is it really important that your ObservableCollection
is sorted, or is what you really want is to have the display in GUI sorted?
I assume that the aim is to have a sorted display that will be updated "real time". Then I see 2 solutions
get the ICollectionView
of your ObservableCollection
and sort it, as explained here
http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/
bind your ObservableCollection
to a CollectionViewsource
, add a sort on it, then use thatCollectionViewSource
as the ItemSource
of a ListView
.
i.e:
add this namespace
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
then
<CollectionViewSource x:Key='src' Source="{Binding MyObservableCollection, ElementName=MainWindowName}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="MyField" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
and bind like this
<ListView ItemsSource="{Binding Source={StaticResource src}}" >
Also using LINQ/Extensionmethod one can aviod firing the NotifyPropertyChanged Event by not setting the source col to the sorted one, but clear the original and add the items of the sorted one. (this will continue fire the Collectionchanged Event, if implemented).
<Extension>
Public Sub SortByProp(Of T)(ByRef c As ICollection(Of T), PropertyName As String)
Dim l = c.ToList
Dim sorted = l.OrderBy(Function(x) x.GetType.GetProperty(PropertyName).GetValue(x))
c.Clear()
For Each i In sorted
c.Add(i)
Next
End Sub
The problem with sorting an ObservableCollection is that every time you change the collection, an event will get fired off. So for a sort that is removing items from one position and adding them to another, you will end up having a ton of events firing.
I think you're best bet is to just insert the stuff into the ObservableCollection in the proper order to begin with. Removing items from the collection won't effect ordering. I whipped up a quick extension method to illustrate
public static void InsertSorted<T>(this ObservableCollection<T> collection, T item, Comparison<T> comparison)
{
if (collection.Count == 0)
collection.Add(item);
else
{
bool last = true;
for (int i = 0; i < collection.Count; i++)
{
int result = comparison.Invoke(collection[i], item);
if (result >= 1)
{
collection.Insert(i, item);
last = false;
break;
}
}
if (last)
collection.Add(item);
}
}
So if you were to use strings (for instance), the code would look like this
ObservableCollection<string> strs = new ObservableCollection<string>();
Comparison<string> comparison = new Comparison<string>((s1, s2) => { return String.Compare(s1, s2); });
strs.InsertSorted("Mark", comparison);
strs.InsertSorted("Tim", comparison);
strs.InsertSorted("Joe", comparison);
strs.InsertSorted("Al", comparison);
Edit
You can keep the calls identical if you extend the ObservableCollection and supply your own insert/add methods. Something like this:
public class BarDataCollection : ObservableCollection<BarData>
{
private Comparison<BarData> _comparison = new Comparison<BarData>((bd1, bd2) => { return DateTime.Compare(bd1.StartDate, bd2.StartDate); });
public new void Insert(int index, BarData item)
{
InternalInsert(item);
}
protected override void InsertItem(int index, BarData item)
{
InternalInsert(item);
}
public new void Add(BarData item)
{
InternalInsert(item);
}
private void InternalInsert(BarData item)
{
if (Items.Count == 0)
Items.Add(item);
else
{
bool last = true;
for (int i = 0; i < Items.Count; i++)
{
int result = _comparison.Invoke(Items[i], item);
if (result >= 1)
{
Items.Insert(i, item);
last = false;
break;
}
}
if (last)
Items.Add(item);
}
}
}
The insert index is ignored.
BarData db1 = new BarData(DateTime.Now.AddDays(-1));
BarData db2 = new BarData(DateTime.Now.AddDays(-2));
BarData db3 = new BarData(DateTime.Now.AddDays(1));
BarData db4 = new BarData(DateTime.Now);
BarDataCollection bdc = new BarDataCollection();
bdc.Add(db1);
bdc.Insert(100, db2);
bdc.Insert(1, db3);
bdc.Add(db4);