Reputation: 749
I originally set the datasource of the DGV to a SortableBindingList. When I run the program, I am able to click any column header and sort ascending or descending by the column.
I have implemented a Filter textbox that filters the data in the DGV using LINQ. After I filter the list with LINQ. I rebind the filtered list to the DGV, but the previously sorted column is no longer sorted.
Should the DGV keep sorting even after I rebind a new datasource?
The only workaround I have come up with for this is to store the current SortedColumn Index and current SortOrder into variables and then reset those properties when binding the new datasource.
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;
}
Update 1: (New Code)
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);
}
Update 2: (Additional Code)
/// <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();
}
}
Upvotes: 3
Views: 2834
Reputation: 205849
DataGridView
does not contain own sorting capability, but instead relies on the data source to do that, and more specifically the IBindingList
implementation. Additional to sorting, IBindingList
provides a ListChanged
event which can be used to update any UI attached to it. Note that IBindingList
is also a list, i.e. you can add/remove/update items as with a normal list, hence you don't need to create a new list and reassign it as a data source. Instead, create it once, attach the UI to it, and then at anytime just update the list content and the UI will reflect the changes automatically - one of the data binding "magics".
Now, in order to make all that happen, IBindingList
must be implemented correctly. BindingList<T>
already provides the most of the functionality required, but unfortunately that does not include sorting. The problems you are experiencing originate from the fact that you are using a buggy third party component (as usual when there is no standard one).
So, instead of MySortableBindindingList<T>
use the following implementation:
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();
}
}
}
which also supports replacing the content and user defined filter.
Here is an example how it can be used, just to get the idea, but I guess you can easily map it to your specific needs:
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);
}
}
}
Upvotes: 3
Reputation: 4728
You're going to need to reapply the sort each time, because you're applying a whole new datasource.
See if you can adapt something like this:
C# - code against a property using the property name as a string
Upvotes: 0