как отсортировать ObservableCollection - PullRequest
16 голосов
/ 02 сентября 2011

У меня есть ObservableCollection, и пользовательский контроль WPF привязан к нему. Элемент управления представляет собой график, показывающий вертикальную полосу для каждого элемента типа BarData в ObservableCollection.

ObservableCollection<BarData>

class BarData
{
   public DateTime StartDate {get; set;}
   public double MoneySpent {get; set;}
   public double TotalMoneySpentTillThisBar {get; set;}
}

Теперь я хочу отсортировать ObservableCollection на основе StartDate так, чтобы данные BarData были в порядке возрастания StartDate в коллекции. Затем я могу вычислить значения TotalMoneySpentTillThisBar в каждом BarData следующим образом:

var collection = new ObservableCollection<BarData>();
//add few BarData objects to collection
collection.Sort(bar => bar.StartData);    // this is ideally the kind of function I was looking for which does not exist 
double total = 0.0;
collection.ToList().ForEach(bar => {
                                     bar.TotalMoneySpentTillThisBar = total + bar.MoneySpent;
                                     total = bar.TotalMoneySpentTillThisBar; 
                                   }
                            );

Я знаю, что могу использовать ICollectionView для сортировки, фильтрации данных для просмотра, но это не меняет фактическую коллекцию. Мне нужно отсортировать фактическую коллекцию, чтобы я мог рассчитать TotalMoneySpentTillThisBar для каждого элемента. Его стоимость зависит от порядка предметов в коллекции.

Спасибо.

Ответы [ 5 ]

34 голосов
/ 02 сентября 2011

Хмммм Первый вопрос, который у меня есть к вам: действительно ли важно, чтобы ваш ObservableCollection был отсортирован, или вы действительно хотите, чтобы дисплей в GUI был отсортирован?

Я предполагаю, что цель состоит в том, чтобы иметь отсортированный дисплей, который будет обновляться в режиме реального времени.Затем я вижу 2 решения

  1. получить ICollectionView вашего ObservableCollection и отсортировать его, как описано здесь http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/

  2. связать ваш ObservableCollection к CollectionViewsource, добавьте к нему сортировку, а затем используйте CollectionViewSource как ItemSource для ListView.

т.е.:

добавьте это пространство имен

xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"

затем

<CollectionViewSource x:Key='src' Source="{Binding MyObservableCollection, ElementName=MainWindowName}">
    <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="MyField" />
    </CollectionViewSource.SortDescriptions>

</CollectionViewSource>

и выполните привязку вот так

<ListView ItemsSource="{Binding Source={StaticResource src}}" >
15 голосов
/ 02 сентября 2011

Я только что создал класс, который расширяет ObservableCollection, потому что со временем я также хотел использовать другие функции, которые я привык использовать от List (Contains, IndexOf, AddRange, RemoveRange и т. Д.)

Я обычно использую его с чем-то вроде

MyCollection.Sort(p => p.Name);

Вот моя реализация сортировки

/// <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
}
12 голосов
/ 02 сентября 2011

Проблема с сортировкой ObservableCollection заключается в том, что при каждом изменении коллекции событие будет запускаться. Таким образом, для сортировки, которая удаляет предметы из одной позиции и добавляет их в другую, в конечном итоге вы получите массу событий.

Я думаю, что вам лучше всего просто вставить материал в ObservableCollection в правильном порядке для начала. Удаление предметов из коллекции не повлияет на порядок. Я показал быстрый метод расширения, чтобы проиллюстрировать

    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);
        }
    }

Так что, если бы вы использовали строки (например), код выглядел бы так:

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

Редактировать

Вы можете сохранить вызовы идентичными, если вы расширите ObservableCollection и предоставите свои собственные методы вставки / добавления. Примерно так:

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);
        }
    }
}

Индекс вставки игнорируется.

        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);
1 голос
/ 24 ноября 2015

Как насчет сортировки данных с использованием LINQ для другой коллекции:

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

Это сработало для меня.

0 голосов
/ 05 октября 2016

Также с помощью метода LINQ / Extension можно избежать события NotifyPropertyChanged, не устанавливая исходное col в отсортированный, но очистив оригинал и добавив элементы отсортированного. (это продолжит вызывать Событие Коллекции, если оно реализовано).

<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
...