[英]DataGridView sort and e.g. BindingList<T> in .NET
我在我的Windows窗体中使用BindingList<T>
,其中包含“ IComparable<Contact>
”联系对象的列表。 现在我希望用户能够按网格中显示的任何列进行排序。
有一种在MSDN在线上描述的方法,它显示了如何基于允许排序的BindingList<T>
实现自定义集合。 但是,是否有一个Sort-event或者某些东西可以在DataGridView中捕获(或者,甚至更好,在BindingSource上)以使用自定义代码对底层集合进行排序?
我真的不喜欢MSDN描述的方式。 另一种方法是我可以轻松地将LINQ查询应用于集合。
我用谷歌搜索并尝试了更多的时间......
到目前为止,.NET中没有内置方法。 您必须基于BindingList<T>
实现自定义类。 自定义数据绑定,第2部分(MSDN)中描述了一种方法。 我最终生成了ApplySortCore
-method的不同实现,以提供与项目ApplySortCore
的实现。
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
{
List<T> itemsList = (List<T>)this.Items;
if(property.PropertyType.GetInterface("IComparable") != null)
{
itemsList.Sort(new Comparison<T>(delegate(T x, T y)
{
// Compare x to y if x is not null. If x is, but y isn't, we compare y
// to x and reverse the result. If both are null, they're equal.
if(property.GetValue(x) != null)
return ((IComparable)property.GetValue(x)).CompareTo(property.GetValue(y)) * (direction == ListSortDirection.Descending ? -1 : 1);
else if(property.GetValue(y) != null)
return ((IComparable)property.GetValue(y)).CompareTo(property.GetValue(x)) * (direction == ListSortDirection.Descending ? 1 : -1);
else
return 0;
}));
}
isSorted = true;
sortProperty = property;
sortDirection = direction;
}
使用这个,您可以按实现IComparable
任何成员进行排序。
然而,虽然这为低数据量提供了出色的结果,但在处理大数据量时,由于反射,性能不是很好。
我用一组简单的数据对象运行测试,计算100000个元素。 按整数类型属性排序大约需要1分钟。 我将进一步细节的实现将此更改为~200ms。
基本思想是使强类型比较受益,同时保持ApplySortCore方法的通用性。 以下内容将通用比较委托替换为对派生类中实现的特定比较器的调用:
SortableBindingList <T>中的新增内容:
protected abstract Comparison<T> GetComparer(PropertyDescriptor prop);
ApplySortCore更改为:
protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
{
List<T> itemsList = (List<T>)this.Items;
if (prop.PropertyType.GetInterface("IComparable") != null)
{
Comparison<T> comparer = GetComparer(prop);
itemsList.Sort(comparer);
if (direction == ListSortDirection.Descending)
{
itemsList.Reverse();
}
}
isSortedValue = true;
sortPropertyValue = prop;
sortDirectionValue = direction;
}
现在,在派生类中,必须为每个可排序属性实现比较器:
class MyBindingList:SortableBindingList<DataObject>
{
protected override Comparison<DataObject> GetComparer(PropertyDescriptor prop)
{
Comparison<DataObject> comparer;
switch (prop.Name)
{
case "MyIntProperty":
comparer = new Comparison<DataObject>(delegate(DataObject x, DataObject y)
{
if (x != null)
if (y != null)
return (x.MyIntProperty.CompareTo(y.MyIntProperty));
else
return 1;
else if (y != null)
return -1;
else
return 0;
});
break;
// Implement comparers for other sortable properties here.
}
return comparer;
}
}
}
这种变体需要更多的代码,但是,如果性能是一个问题,我认为值得付出努力。
我知道所有这些答案在撰写时都很好。 可能他们仍然是。 我正在寻找类似的东西,并找到了一个替代解决方案,将任何列表或集合转换为可排序的BindingList<T>
。
以下是重要的片段(下面分享了完整示例的链接):
void Main()
{
DataGridView dgv = new DataGridView();
dgv.DataSource = new ObservableCollection<Person>(Person.GetAll()).ToBindingList();
}
此解决方案使用Entity Framework库中提供的扩展方法。 因此,在继续进行之前,请考虑以下事项:
如您所见,您可以通过单击DataGridView控件上的列标题对所有四列不同数据类型进行排序。
那些没有LINQPad的人,仍然可以下载查询并用记事本打开它,看看完整的样本。
这是一个使用一些新技巧的新建议。
IList<T>
的基础类型必须实现void Sort(Comparison<T>)
或者您必须传入委托来为您调用sort函数。 ( IList<T>
没有void Sort(Comparison<T>)
函数)
在静态构造函数期间,类将通过类型T
查找实现ICompareable
或ICompareable<T>
所有公共实例属性,并缓存它创建的代理以供以后使用。 这是在一个静态构造函数中完成的,因为我们只需要为每个类型的T
和Dictionary<TKey,TValue>
做一次Dictionary<TKey,TValue>
在读取时是线程安全的。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace ExampleCode
{
public class SortableBindingList<T> : BindingList<T>
{
private static readonly Dictionary<string, Comparison<T>> PropertyLookup;
private readonly Action<IList<T>, Comparison<T>> _sortDelegate;
private bool _isSorted;
private ListSortDirection _sortDirection;
private PropertyDescriptor _sortProperty;
//A Dictionary<TKey, TValue> is thread safe on reads so we only need to make the dictionary once per type.
static SortableBindingList()
{
PropertyLookup = new Dictionary<string, Comparison<T>>();
foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Type propertyType = property.PropertyType;
bool usingNonGenericInterface = false;
//First check to see if it implments the generic interface.
Type compareableInterface = propertyType.GetInterfaces()
.FirstOrDefault(a => a.Name == "IComparable`1" &&
a.GenericTypeArguments[0] == propertyType);
//If we did not find a generic interface then use the non-generic interface.
if (compareableInterface == null)
{
compareableInterface = propertyType.GetInterface("IComparable");
usingNonGenericInterface = true;
}
if (compareableInterface != null)
{
ParameterExpression x = Expression.Parameter(typeof(T), "x");
ParameterExpression y = Expression.Parameter(typeof(T), "y");
MemberExpression xProp = Expression.Property(x, property.Name);
Expression yProp = Expression.Property(y, property.Name);
MethodInfo compareToMethodInfo = compareableInterface.GetMethod("CompareTo");
//If we are not using the generic version of the interface we need to
// cast to object or we will fail when using structs.
if (usingNonGenericInterface)
{
yProp = Expression.TypeAs(yProp, typeof(object));
}
MethodCallExpression call = Expression.Call(xProp, compareToMethodInfo, yProp);
Expression<Comparison<T>> lambada = Expression.Lambda<Comparison<T>>(call, x, y);
PropertyLookup.Add(property.Name, lambada.Compile());
}
}
}
public SortableBindingList() : base(new List<T>())
{
_sortDelegate = (list, comparison) => ((List<T>)list).Sort(comparison);
}
public SortableBindingList(IList<T> list) : base(list)
{
MethodInfo sortMethod = list.GetType().GetMethod("Sort", new[] {typeof(Comparison<T>)});
if (sortMethod == null || sortMethod.ReturnType != typeof(void))
{
throw new ArgumentException(
"The passed in IList<T> must support a \"void Sort(Comparision<T>)\" call or you must provide one using the other constructor.",
"list");
}
_sortDelegate = CreateSortDelegate(list, sortMethod);
}
public SortableBindingList(IList<T> list, Action<IList<T>, Comparison<T>> sortDelegate)
: base(list)
{
_sortDelegate = sortDelegate;
}
protected override bool IsSortedCore
{
get { return _isSorted; }
}
protected override ListSortDirection SortDirectionCore
{
get { return _sortDirection; }
}
protected override PropertyDescriptor SortPropertyCore
{
get { return _sortProperty; }
}
protected override bool SupportsSortingCore
{
get { return true; }
}
private static Action<IList<T>, Comparison<T>> CreateSortDelegate(IList<T> list, MethodInfo sortMethod)
{
ParameterExpression sourceList = Expression.Parameter(typeof(IList<T>));
ParameterExpression comparer = Expression.Parameter(typeof(Comparison<T>));
UnaryExpression castList = Expression.TypeAs(sourceList, list.GetType());
MethodCallExpression call = Expression.Call(castList, sortMethod, comparer);
Expression<Action<IList<T>, Comparison<T>>> lambada =
Expression.Lambda<Action<IList<T>, Comparison<T>>>(call,
sourceList, comparer);
Action<IList<T>, Comparison<T>> sortDelegate = lambada.Compile();
return sortDelegate;
}
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
{
Comparison<T> comparison;
if (PropertyLookup.TryGetValue(property.Name, out comparison))
{
if (direction == ListSortDirection.Descending)
{
_sortDelegate(Items, (x, y) => comparison(y, x));
}
else
{
_sortDelegate(Items, comparison);
}
_isSorted = true;
_sortProperty = property;
_sortDirection = direction;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, property));
}
}
protected override void RemoveSortCore()
{
_isSorted = false;
}
}
}
这是一个非常干净的替代方案,在我的情况下工作得很好。 我已经设置了特定的比较函数用于List.Sort(比较),所以我只是从其他StackOverflow示例的部分中进行了调整。
class SortableBindingList<T> : BindingList<T>
{
public SortableBindingList(IList<T> list) : base(list) { }
public void Sort() { sort(null, null); }
public void Sort(IComparer<T> p_Comparer) { sort(p_Comparer, null); }
public void Sort(Comparison<T> p_Comparison) { sort(null, p_Comparison); }
private void sort(IComparer<T> p_Comparer, Comparison<T> p_Comparison)
{
if(typeof(T).GetInterface(typeof(IComparable).Name) != null)
{
bool originalValue = this.RaiseListChangedEvents;
this.RaiseListChangedEvents = false;
try
{
List<T> items = (List<T>)this.Items;
if(p_Comparison != null) items.Sort(p_Comparison);
else items.Sort(p_Comparer);
}
finally
{
this.RaiseListChangedEvents = originalValue;
}
}
}
}
不适用于自定义对象。 在.Net 2.0中,我不得不使用BindingList滚动我的排序。 .Net 3.5中可能有新内容,但我还没有考虑过。 现在有LINQ和排序选项,如果现在可能更容易实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.