我有一个ObservableCollection和一个WPF UserControl是Databound。 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。它的价值取决于集合中的项目顺序。
感谢。
答案 0 :(得分:32)
ObservableCollection
的排序是非常重要的,或者您真正想要的是将GUI中的显示排序?
我认为目标是有一个“实时”更新的排序显示。然后我看到2个解决方案
获取ICollectionView
的{{1}}并对其进行排序,如此处所述
http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/
将您的ObservableCollection
绑定到ObservableCollection
,在其上添加排序,然后将CollectionViewsource
用作CollectionViewSource
的{{1}}。
即:
添加此命名空间
ItemSource
然后
ListView
并像这样绑定
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
答案 1 :(得分:14)
我刚刚创建了一个扩展ObservableCollection
的类,因为随着时间的推移,我还想要使用List
Contains
(IndexOf
{{1} }},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
}
答案 2 :(得分:11)
对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);
答案 3 :(得分:1)
如何使用不同集合上的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);
这对我有用。
答案 4 :(得分:0)
同样使用LINQ / Extension方法,可以通过不将源col设置为已排序的col来触发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
答案 5 :(得分:0)
我知道这是旧帖子,但我对大多数解决方案都不满意,因为它破坏了绑定。所以如果有人遇到,这就是我所做的。您可以为更多的属性排序进行更多的重载。
这不会破坏绑定。
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);