C# Winforms DataGridView绑定新数据源后,排序列丢失

3

我最初将 DGV 的数据源设置为 SortableBindingList。当我运行程序时,可以单击任何列标题并按该列升序或降序排序。

我已经使用 LINQ 实现了一个筛选文本框,用于筛选 DGV 中的数据。在我使用 LINQ 对列表进行筛选后,我会将筛选后的列表重新绑定到 DGV,但之前排序的列不再排序。

在重新绑定新的数据源后,DGV 是否应该保持排序?

我想到的唯一解决方法是将当前 SortedColumn Index 和当前 SortOrder 存储到变量中,然后在绑定新的数据源时重置这些属性。

private void PopulateGrid()
{
     var gridSource = new MySortableBindingList<Case>(_caseList);
     dataGridView_Cases.DataSource = gridSource;
     ConfigureGrid();
}

private void ApplyFilter(string fString)
{
        MySortableBindingList<Case> msbList = new MySortableBindingList<Case>(_caseList.Where(x => (x.StudentLastName.IndexOf(fString, StringComparison.OrdinalIgnoreCase) >= 0) || (x.StudentIDDisplay.ToString().IndexOf(fString, StringComparison.OrdinalIgnoreCase) >= 0)).ToList());
        dataGridView_Cases.DataSource = msbList;                       
}

更新1:(新代码)

    private MySortableBindingList<Case> _gridSource = new MySortableBindingList<Case>();
    BindingSource _caseBindingSource = new BindingSource();
    private void PopulateGrid()
    {
        _gridSource = new MySortableBindingList<Case>(_caseList);
        _caseBindingSource.DataSource = _gridSource;
        dataGridView_Cases.DataSource = _caseBindingSource;
        ConfigureGrid();
    }

 private void ApplyFilter(string fString)
 {
     _gridSource.Clear();
     foreach (var fCase in _caseList.Where(x => (x.StudentLastName.IndexOf(fString, StringComparison.OrdinalIgnoreCase) >= 0) || (x.StudentIDDisplay.ToString().IndexOf(fString, StringComparison.OrdinalIgnoreCase) >= 0)).ToList())
       {
          _gridSource.Add(fCase);
       }
      _caseBindingSource.ResetBindings(false);
 }

更新 2: (附加代码)

/// <summary>
/// Source: http://www.codeproject.com/Articles/31418/Implementing-a-Sortable-BindingList-Very-Very-Quic
/// </summary>
/// <typeparam name="T"></typeparam>
public class MySortableBindingList<T> : BindingList<T>
{

    // reference to the list provided at the time of instantiation
    List<T> originalList;
    ListSortDirection sortDirection;
    PropertyDescriptor sortProperty;

    // function that refereshes the contents
    // of the base classes collection of elements
    Action<MySortableBindingList<T>, List<T>>
                   populateBaseList = (a, b) => a.ResetItems(b);

    // a cache of functions that perform the sorting
    // for a given type, property, and sort direction
    static Dictionary<string, Func<List<T>, IEnumerable<T>>>
       cachedOrderByExpressions = new Dictionary<string, Func<List<T>,
                                                 IEnumerable<T>>>();
    /// <summary>
    /// Create a sortable binding list
    /// </summary>
    public MySortableBindingList()
    {
        originalList = new List<T>();
    }

            /// <summary>
    /// Create a sortable binding list
    /// </summary>
    public MySortableBindingList(IEnumerable<T> enumerable)
    {
        originalList = enumerable.ToList();
        populateBaseList(this, originalList);
    }

    /// <summary>
    /// Create a sortable binding list
    /// </summary>
    public MySortableBindingList(List<T> list)
    {
        originalList = list;
        populateBaseList(this, originalList);
    }

    /// <summary>
    /// Look for an appropriate sort method in the cache if not found .
    /// Call CreateOrderByMethod to create one. 
    /// Apply it to the original list.
    /// Notify any bound controls that the sort has been applied.
    /// </summary>
    /// <param name="prop"></param>
    /// <param name="direction"></param>
    protected override void ApplySortCore(PropertyDescriptor prop,
                            ListSortDirection direction)
    {
        /*
         Look for an appropriate sort method in the cache if not found .
         Call CreateOrderByMethod to create one. 
         Apply it to the original list.
         Notify any bound controls that the sort has been applied.
         */

        sortProperty = prop;

        var orderByMethodName = sortDirection ==
            ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
        var cacheKey = typeof(T).GUID + prop.Name + orderByMethodName;

        if (!cachedOrderByExpressions.ContainsKey(cacheKey))
        {
            CreateOrderByMethod(prop, orderByMethodName, cacheKey);
        }

        ResetItems(cachedOrderByExpressions[cacheKey](originalList).ToList());
        ResetBindings();
        sortDirection = sortDirection == ListSortDirection.Ascending ?
                        ListSortDirection.Descending : ListSortDirection.Ascending;
    }


    private void CreateOrderByMethod(PropertyDescriptor prop,
                 string orderByMethodName, string cacheKey)
    {

        /*
         Create a generic method implementation for IEnumerable<T>.
         Cache it.
        */

        var sourceParameter = Expression.Parameter(typeof(List<T>), "source");
        var lambdaParameter = Expression.Parameter(typeof(T), "lambdaParameter");
        var accesedMember = typeof(T).GetProperty(prop.Name);
        var propertySelectorLambda =
            Expression.Lambda(Expression.MakeMemberAccess(lambdaParameter,
                              accesedMember), lambdaParameter);
        var orderByMethod = typeof(Enumerable).GetMethods()
                                      .Where(a => a.Name == orderByMethodName &&
                                                   a.GetParameters().Length == 2)
                                      .Single()
                                      .MakeGenericMethod(typeof(T), prop.PropertyType);

        var orderByExpression = Expression.Lambda<Func<List<T>, IEnumerable<T>>>(
                                    Expression.Call(orderByMethod,
                                            new Expression[] { sourceParameter, 
                                                           propertySelectorLambda }),
                                            sourceParameter);

        cachedOrderByExpressions.Add(cacheKey, orderByExpression.Compile());
    }

    /// <summary>
    /// RemoveSortCore
    /// </summary>
    protected override void RemoveSortCore()
    {
        ResetItems(originalList);
    }

    private void ResetItems(List<T> items)
    {

        base.ClearItems();

        for (int i = 0; i < items.Count; i++)
        {
            base.InsertItem(i, items[i]);
        }
    }

    /// <summary>
    /// SupportsSortingCore
    /// </summary>
    protected override bool SupportsSortingCore
    {
        get
        {
            // indeed we do
            return true;
        }
    }

    /// <summary>
    /// Ascending or descending
    /// </summary>
    protected override ListSortDirection SortDirectionCore
    {
        get
        {
            return sortDirection;
        }
    }

    /// <summary>
    /// A property
    /// </summary>
    protected override PropertyDescriptor SortPropertyCore
    {
        get
        {
            return sortProperty;
        }
    }

    /// <summary>
    /// List has changed
    /// </summary>
    /// <param name="e"></param>
    protected override void OnListChanged(ListChangedEventArgs e)
    {
        originalList = base.Items.ToList();
    }
}

为什么你总是重新分配数据源?只需分配一次,然后修改内容并引发重置事件。 - Ivan Stoev
@IvanStoev 我如何在绑定为数据源后修改内容?什么是重置事件? - Michael
https://msdn.microsoft.com/en-us/library/ms132702(v=vs.110).aspx - Ivan Stoev
我已经使用这个方法多年了,它确实有效。但是你需要先替换绑定列表的内容(使用Clear、Add等方法),而不是使用new MySortableBindingList - Ivan Stoev
@IvanStoev 我已经在我的原始帖子中添加了MySortableBindingList类的代码。 - Michael
显示剩余4条评论
2个回答

3
DataGridView没有自己的排序功能,而是依赖于数据源来实现,尤其是 IBindingList 接口。除了排序外,IBindingList 还提供了一个 ListChanged 事件,可以用来更新任何附加到它的 UI。请注意,IBindingList 也是一个列表,即您可以像普通列表一样添加/删除/更新项目,因此您不需要创建一个新列表并将其重新分配为数据源。相反,只需创建一次,将 UI 附加到它上面,然后随时更新列表内容,UI 将自动反映更改 - 这是数据绑定的“魔力”之一。
为了实现这一切,必须正确实现 IBindingList 接口。 BindingList<T> 已经提供了大部分所需的功能,但不幸的是,它并不包括排序。您遇到的问题源于使用有缺陷的第三方组件(通常情况下没有标准组件)。
因此,请使用以下实现,而不是 MySortableBindindingList<T>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
namespace Tests
{
    public class MyBindingList<T> : BindingList<T>
    {
        static readonly Dictionary<string, Func<IEnumerable<T>, IEnumerable<T>>> orderByMethodCache = new Dictionary<string, Func<IEnumerable<T>, IEnumerable<T>>>();
        private static Func<IEnumerable<T>, IEnumerable<T>> GetOrderByMethod(PropertyDescriptor prop, ListSortDirection direction)
        {
            var orderByMethodName = direction == ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
            var cacheKey = typeof(T).GUID + prop.Name + orderByMethodName;
            Func<IEnumerable<T>, IEnumerable<T>> orderByMethod;
            if (!orderByMethodCache.TryGetValue(cacheKey, out orderByMethod))
                orderByMethodCache.Add(cacheKey, orderByMethod = CreateOrderByMethod(prop, orderByMethodName));
            return orderByMethod;
        }
        private static Func<IEnumerable<T>, IEnumerable<T>> CreateOrderByMethod(PropertyDescriptor prop, string orderByMethodName)
        {
            var source = Expression.Parameter(typeof(IEnumerable<T>), "source");
            var item = Expression.Parameter(typeof(T), "item");
            var member = Expression.Property(item, prop.Name);
            var selector = Expression.Lambda(member, item);
            var orderByMethod = typeof(Enumerable).GetMethods()
                .Single(a => a.Name == orderByMethodName && a.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), member.Type);
            var orderByExpression = Expression.Lambda<Func<IEnumerable<T>, IEnumerable<T>>>(
                Expression.Call(orderByMethod, new Expression[] { source, selector }), source);
            return orderByExpression.Compile();
        }
        List<T> originalList = new List<T>();
        ListSortDirection sortDirection;
        PropertyDescriptor sortProperty;
        bool isSorted;
        bool ignoreListChanged;
        Func<T, bool> filter;
        public MyBindingList() { }
        public MyBindingList(IEnumerable<T> items) { Update(items); }
        protected override bool SupportsSortingCore { get { return true; } }
        protected override PropertyDescriptor SortPropertyCore { get { return sortProperty; } }
        protected override ListSortDirection SortDirectionCore { get { return sortDirection; } }
        protected override bool IsSortedCore { get { return isSorted; } }
        public Func<T, bool> Filter
        {
            get { return filter; }
            set
            {
                filter = value;
                Refresh();
            }
        }
        public void Update(IEnumerable<T> items)
        {
            originalList.Clear();
            originalList.AddRange(items);
            Refresh();
        }
        public void Refresh()
        {
            var items = originalList.AsEnumerable();
            if (Filter != null)
                items = items.Where(filter);
            if (isSorted)
                items = GetOrderByMethod(sortProperty, sortDirection)(items);

            bool raiseListChangedEvents = RaiseListChangedEvents;
            RaiseListChangedEvents = false;
            base.ClearItems();
            foreach (var item in items)
                Add(item);
            RaiseListChangedEvents = raiseListChangedEvents;
            if (!raiseListChangedEvents) return;
            ignoreListChanged = true;
            ResetBindings();
            ignoreListChanged = false;
        }
        protected override void OnListChanged(ListChangedEventArgs e)
        {
            if (!ignoreListChanged)
                originalList = Items.ToList();
            base.OnListChanged(e);
        }
        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            var orderByMethod = GetOrderByMethod(prop, direction);
            sortProperty = prop;
            sortDirection = direction;
            isSorted = true;
            Refresh();
        }
        protected override void RemoveSortCore()
        {
            if (!isSorted) return;
            isSorted = false;
            Refresh();
        }
    }
}

这个工具还支持替换内容和用户定义的过滤器。

以下是一个示例,以帮助您了解如何使用它,但我想您可以很容易地将其映射到您的特定需求:

using System;
using System.Windows.Forms;
namespace Tests
{
    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var form = new Form();
            var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
            var filterBox = new TextBox { Dock = DockStyle.Bottom, Parent = form };
            var data = new MyBindingList<Person>(new[]
            {
                new Person { FirstName = "Jon", LastName = "Skeet" },
                new Person { FirstName = "Hans", LastName = "Passant" },
                new Person { FirstName = "Ivan", LastName = "Stoev" },
            });
            dg.DataSource = data;
            var filterText = string.Empty;
            filterBox.TextChanged += (sender, e) =>
            {
                var text = filterBox.Text.Trim();
                if (filterText == text) return;
                filterText = text;
                if (!string.IsNullOrEmpty(filterText))
                    data.Filter = person => person.FirstName.Contains(filterText) || person.LastName.Contains(filterText);
                else
                    data.Filter = null;
            };
            Application.Run(form);
        }
    }
}

1
非常完美的工作 :) 非常感谢您的所有帮助! - Michael

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接