wpf 如何对 ObservableCollection 进行排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7284805/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 23:03:47  来源:igfitidea点击:

how to sort ObservableCollection

wpfsortingobservablecollectionicollectionview

提问by Souvik Basu

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

我有一个 ObservableCollection 和一个 WPF UserControl 数据绑定到它。Control 是一个图表,它显示 ObservableCollection 中 BarData 类型的每个项目的垂直条。

ObservableCollection<BarData>

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

Now I want to sort out the ObservableCollection based on StartDate so that the BarData's will be in increasing order of StartDate in the collection. Then I can calculate values of TotalMoneySpentTillThisBar in each BarData like this -

现在我想根据 StartDate 整理 ObservableCollection,以便 BarData 在集合中按 StartDate 的递增顺序排列。然后我可以像这样计算每个 BarData 中 TotalMoneySpentTillThisBar 的值 -

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

I know I can use ICollectionView to sort, filter data for veiwing but that does not change the actual collection. I need to sort the actual collection so that I can calculate TotalMoneySpentTillThisBar for each item. Its value depends on order of items in colection.

我知道我可以使用 ICollectionView 对数据进行排序、过滤以进行查看,但这不会改变实际的集合。我需要对实际集合进行排序,以便可以计算每个项目的 TotalMoneySpentTillThisBar。它的值取决于收集项目的顺序。

Thanks.

谢谢。

回答by Gregfr

hummm first question I have for you is: is it really important that your ObservableCollectionis sorted, or is what you really want is to have the display in GUI sorted?

嗯,我对您的第一个问题是:对您ObservableCollection的排序是否真的很重要,或者您真正想要的是对 GUI 中的显示进行排序?

I assume that the aim is to have a sorted display that will be updated "real time". Then I see 2 solutions

我假设目标是有一个将“实时”更新的排序显示。然后我看到 2 个解决方案

  1. get the ICollectionViewof your ObservableCollectionand sort it, as explained here http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/

  2. bind your ObservableCollectionto a CollectionViewsource, add a sort on it, then use thatCollectionViewSourceas the ItemSourceof a ListView.

  1. 获取ICollectionView您的ObservableCollection并对其进行排序,如此处所述 http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/

  2. 将您的绑定ObservableCollection到 a CollectionViewsource,在其上添加一个排序,然后将其CollectionViewSource用作ItemSourcea 的ListView

i.e:

IE:

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}}" >

回答by Rachel

I just created a class that extends the ObservableCollectionbecause over time I've also wanted other functionality that I'm used to using from a List(Contains, IndexOf, AddRange, RemoveRange, etc)

我刚刚创建了一个扩展的类,ObservableCollection因为随着时间的推移,我还想要其他我习惯使用的功能List( Contains, IndexOf, AddRange, RemoveRange, 等)

I usually use it with something like

我通常将它与类似的东西一起使用

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

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
}

回答by mdm20

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.

对 ObservableCollection 进行排序的问题在于,每次更改集合时,都会触发一个事件。因此,对于从一个位置删除项目并将它们添加到另一个位置的排序,您最终将触发大量事件。

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

我认为你最好的办法是按照正确的顺序将内容插入 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);
        }
    }

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:

如果扩展 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);
        }
    }
}

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

回答by gagy12

What about sorting the data using LINQ on the different collection:

在不同的集合上使用 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);

This worked for me.

这对我有用。

回答by dba

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

同样使用 LINQ/Extensionmethod 可以避免触发 NotifyPropertyChanged 事件,方法是不将源 col 设置为已排序的列,而是清除原始列并添加已排序的项。(如果实施,这将继续触发 Collectionchanged 事件)。

<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