【问题标题】:C# Winforms DataGridView Sorted column lost when binding new DatasourceC# Winforms DataGridView 绑定新数据源时排序列丢失
【发布时间】:2015-10-14 18:32:30
【问题描述】:

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

我已经实现了一个过滤器文本框,它使用 LINQ 过滤 DGV 中的数据。 在我用 LINQ 过滤列表之后。我将过滤后的列表重新绑定到 DGV,但之前排序的列不再排序。

即使在我重新绑定新数据源后,DGV 是否应该继续排序?

为此我想出的唯一解决方法是将当前的 SortedColumn 索引和当前的 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();
    }
}

【问题讨论】:

  • 在更改 DataSources 之间不会保留排序顺序是有道理的;考虑新的DataSource 没有旧DataSource 的所有列(读取:已排序的列)的情况。我相信将它们存储在变量中并以编程方式重新分配排序数据是唯一的方法。
  • 为什么总是重新分配数据源?分配一次,然后修改内容并引发 Reset 事件。
  • @IvanStoev 绑定为数据源后如何修改内容?什么重置事件?
  • @IvanStoev 调用 ResetBindings() 方法不会使 datagridview 刷新数据源。似乎重新分配数据源仍然是让 datagridview 更新为最新数据的唯一方法

标签: c# winforms sorting datagridview


【解决方案1】:

DataGridView 不包含自己的排序功能,而是依赖数据源来执行此操作,更具体地说是IBindingList 实现。除了排序之外,IBindingList 提供了一个ListChanged 事件,可用于更新附加到它的任何 UI。请注意,IBindingList 也是一个列表,即您可以像使用普通列表一样添加/删除/更新项目,因此您无需创建新列表并将其重新分配为数据源。相反,创建一次,将 UI 附加到它,然后随时更新列表内容,UI 将自动反映更改 - 数据绑定“魔术”之一。

现在,为了实现这一切,IBindingList 必须正确实施。 BindingList&lt;T&gt; 已经提供了所需的大部分功能,但不幸的是,这不包括排序。您遇到的问题源于您使用有问题的第三方组件(通常没有标准组件时)。

所以,不要使用MySortableBindindingList&lt;T&gt;,而是使用以下实现:

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

【讨论】:

  • 完美运行 :) 非常感谢您的帮助!
【解决方案2】:

您每次都需要重新应用排序,因为您正在应用一个全新的数据源。

看看你能不能适应这样的东西:

C# - code against a property using the property name as a string

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 2019-07-21
    • 1970-01-01
    • 2013-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多