如何对 ObservableCollection 进行排序

发布于 2024-12-02 19:23:31 字数 1177 浏览 0 评论 0原文

我有一个 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;}
}

现在我想根据 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; 
                                   }
                            );

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

谢谢。

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

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.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

孤檠 2024-12-09 19:23:31

嗯,我要问你的第一个问题是:
ObservableCollection 排序真的很重要,还是您真正想要的是 GUI 中的显示排序?

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

  1. 获取 ObservableCollectionICollectionView 并对其进行排序,如此处所述
    http://marlongrech.wordpress.com/2008/11/22/icollectionview-解释/

  2. 将您的 ObservableCollection 绑定到 CollectionViewsource,添加排序然后使用该CollectionViewSource 作为ListViewItemSource

即:

添加此名称空间

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

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

  1. get the ICollectionView of your ObservableCollection and sort it, as explained here
    http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/

  2. 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}}" >
榕城若虚 2024-12-09 19:23:31

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

我通常将它与

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
}

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
}
池予 2024-12-09 19:23:31

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

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);
相权↑美人 2024-12-09 19:23:31

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

这对我有用。

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.

雨轻弹 2024-12-09 19:23:31

另外,使用 LINQ/Extension 方法,可以通过不将源列设置为已排序列,而是清除原始列并添加已排序列的项目来避免触发 NotifyPropertyChanged 事件。 (如果实现的话,这将继续触发 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

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
演多会厌 2024-12-09 19:23:31

我知道这是旧帖子,但我对大多数解决方案不满意,因为它破坏了绑定。所以如果有人遇到,这就是我所做的。您可以进行更多重载以进行更多属性排序。

这不会破坏绑定。

    public static void AddRangeSorted<T, TSort>(this ObservableCollection<T> collection, IEnumerable<T> toAdd, Func<T, TSort> sortSelector, OrderByDirection direction)
    {
        var sortArr = Enumerable.Concat(collection, toAdd).OrderBy(sortSelector, direction).ToList();
        foreach (T obj in toAdd.OrderBy(o => sortArr.IndexOf(o)).ToList())
        {
            collection.Insert(sortArr.IndexOf(obj), obj);
        }
    }

    public static void AddRangeSorted<T, TSort, TSort2>(this ObservableCollection<T> collection, IEnumerable<T> toAdd, Func<T, TSort> sortSelector, OrderByDirection direction, Func<T, TSort2> sortSelector2, OrderByDirection direction2)
    {
        var sortArr = Enumerable.Concat(collection, toAdd).OrderBy(sortSelector, direction).ThenBy(sortSelector2, direction2).ToList();
        foreach (T obj in toAdd.OrderBy(o=> sortArr.IndexOf(o)).ToList())
        {
            collection.Insert(sortArr.IndexOf(obj), obj);
        }
    }

及用法:

OrderLines.AddRangeSorted(toAdd,ol=>ol.ID, OrderByDirection.Ascending);

I know this is old post, but I was unhappy with most of the solutions, because it broke bindings. So if anyone comes across, this is what I did. You can make more overloads for more property sortings.

This doesnt break binding.

    public static void AddRangeSorted<T, TSort>(this ObservableCollection<T> collection, IEnumerable<T> toAdd, Func<T, TSort> sortSelector, OrderByDirection direction)
    {
        var sortArr = Enumerable.Concat(collection, toAdd).OrderBy(sortSelector, direction).ToList();
        foreach (T obj in toAdd.OrderBy(o => sortArr.IndexOf(o)).ToList())
        {
            collection.Insert(sortArr.IndexOf(obj), obj);
        }
    }

    public static void AddRangeSorted<T, TSort, TSort2>(this ObservableCollection<T> collection, IEnumerable<T> toAdd, Func<T, TSort> sortSelector, OrderByDirection direction, Func<T, TSort2> sortSelector2, OrderByDirection direction2)
    {
        var sortArr = Enumerable.Concat(collection, toAdd).OrderBy(sortSelector, direction).ThenBy(sortSelector2, direction2).ToList();
        foreach (T obj in toAdd.OrderBy(o=> sortArr.IndexOf(o)).ToList())
        {
            collection.Insert(sortArr.IndexOf(obj), obj);
        }
    }

And usage:

OrderLines.AddRangeSorted(toAdd,ol=>ol.ID, OrderByDirection.Ascending);
£冰雨忧蓝° 2024-12-09 19:23:31

我正在使用 VMMV 模型。您可以将此代码添加到事件或 btn 或其他内容中。它通过参数 (int,string...)-sortVar 对集合进行排序。

            OrgCollection = new ObservableCollection<ClassModel>();
        var sortedCollection = new ObservableCollection<ClassModel>(OrgCollection.OrderBy(x => x.sortVar));
        OrgCollection.Clear();
        foreach (var x in sortedCollection)
        {
            ClassModel modelToSort = new ClassModel()
            {// 'var y in ClassModel' = x.'var y in ClassModel'
             // e.g. VarName = x.VarName, ...
            };
            OrgCollection.Add(modelToSort);
        }

I'm using VMMV model. You can just add this code to an event or btn or something. and it sorts your collection by parameter (int,string...)-sortVar.

            OrgCollection = new ObservableCollection<ClassModel>();
        var sortedCollection = new ObservableCollection<ClassModel>(OrgCollection.OrderBy(x => x.sortVar));
        OrgCollection.Clear();
        foreach (var x in sortedCollection)
        {
            ClassModel modelToSort = new ClassModel()
            {// 'var y in ClassModel' = x.'var y in ClassModel'
             // e.g. VarName = x.VarName, ...
            };
            OrgCollection.Add(modelToSort);
        }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文