How to Sort Databound Datagridview Column

How to sort databound DataGridView column?

I recall having issues finding something that would work when I added sorting to my datagrids too. You can implement a sortable bindable list by first adding the following class to your project. It is a list implementation that implements BindingList<T>, so that you can bind your datagrid to it, and it also supports sorting. A better explanation of the details than I could give is on MSDN here

public class SortableBindingList<T> : BindingList<T>
{
private ArrayList sortedList;
private ArrayList unsortedItems;
private bool isSortedValue;

public SortableBindingList()
{
}

public SortableBindingList(IList<T> list)
{
foreach (object o in list)
{
this.Add((T)o);
}
}

protected override bool SupportsSearchingCore
{
get
{
return true;
}
}

protected override int FindCore(PropertyDescriptor prop, object key)
{
PropertyInfo propInfo = typeof(T).GetProperty(prop.Name);
T item;

if (key != null)
{
for (int i = 0; i < Count; ++i)
{
item = (T)Items[i];
if (propInfo.GetValue(item, null).Equals(key))
return i;
}
}
return -1;
}

public int Find(string property, object key)
{
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor prop = properties.Find(property, true);

if (prop == null)
return -1;
else
return FindCore(prop, key);
}

protected override bool SupportsSortingCore
{
get { return true; }
}

protected override bool IsSortedCore
{
get { return isSortedValue; }
}

ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;

protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
{
sortedList = new ArrayList();

Type interfaceType = prop.PropertyType.GetInterface("IComparable");

if (interfaceType == null && prop.PropertyType.IsValueType)
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType);

if (underlyingType != null)
{
interfaceType = underlyingType.GetInterface("IComparable");
}
}

if (interfaceType != null)
{
sortPropertyValue = prop;
sortDirectionValue = direction;

IEnumerable<T> query = base.Items;
if (direction == ListSortDirection.Ascending)
{
query = query.OrderBy(i => prop.GetValue(i));
}
else
{
query = query.OrderByDescending(i => prop.GetValue(i));
}
int newIndex = 0;
foreach (object item in query)
{
this.Items[newIndex] = (T)item;
newIndex++;
}
isSortedValue = true;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

}
else
{
throw new NotSupportedException("Cannot sort by " + prop.Name +
". This" + prop.PropertyType.ToString() +
" does not implement IComparable");
}
}

protected override void RemoveSortCore()
{
int position;
object temp;

if (unsortedItems != null)
{
for (int i = 0; i < unsortedItems.Count; )
{
position = this.Find("LastName",
unsortedItems[i].GetType().
GetProperty("LastName").GetValue(unsortedItems[i], null));
if (position > 0 && position != i)
{
temp = this[i];
this[i] = this[position];
this[position] = (T)temp;
i++;
}
else if (position == i)
i++;
else
unsortedItems.RemoveAt(i);
}
isSortedValue = false;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
}

public void RemoveSort()
{
RemoveSortCore();
}
protected override PropertyDescriptor SortPropertyCore
{
get { return sortPropertyValue; }
}

protected override ListSortDirection SortDirectionCore
{
get { return sortDirectionValue; }
}

}

With that in place, the only changes you need to make to the code that you have posted above is to create a SortableBindingList based on your list and bind to the sortable list, rather than the standard one, like so:

List<MyClass> list = new List<MyClass>();
list.Add(new MyClass("Peter", 1202));
list.Add(new MyClass("James", 292));
list.Add(new MyClass("Bond", 23));

// Added sortable list...
SortableBindingList<MyClass> sortableList = new SortableBindingList<MyClass>(list);

BindingSource bs = new BindingSource();
bs.DataSource = sortableList; // Bind to the sortable list

And that will be enough to get you going.

How do I sort a DataBound column in a DataGridView on a column header click?

I created a new IComparer based interface that allows you to specify both a column and a direction. I only did this because I need my sorting code to be as generic as possible - I have TWO grids that need to sort like this, and I don't want to maintain twice the code. Here is the interface, quite simple:

   public interface IByColumnComparer : IComparer
{
string SortColumn { get; set; }
bool SortDescending { get; set; }
}

Obviously, if you're not worried about keeping things generic (you probably should) than this isn't strictly necessary. Then, I built a new class that is based on BindingList<>. This allowed me to override the sorting code and provide my own IByColumnComparer on a column by column basis which is what allowed for the flexibility I needed. Check this out:

public class SortableGenericCollection<T> : BindingList<T>
{
IByColumnComparer GenericComparer = null;

public SortableGenericCollection(IByColumnComparer SortingComparer)
{
GenericComparer = SortingComparer;
}

protected override bool SupportsSortingCore
{
get
{
return true;
}
}

protected override bool IsSortedCore
{
get
{
for (int i = 0; i < Items.Count - 1; ++i)
{
T lhs = Items[i];
T rhs = Items[i + 1];
PropertyDescriptor property = SortPropertyCore;
if (property != null)
{
object lhsValue = lhs == null ? null :
property.GetValue(lhs);
object rhsValue = rhs == null ? null :
property.GetValue(rhs);
int result;
if (lhsValue == null)
{
result = -1;
}
else if (rhsValue == null)
{
result = 1;
}
else
{
result = GenericComparer.Compare(lhs, rhs);
}
if (result >= 0)
{
return false;
}
}
}
return true;
}
}

private ListSortDirection sortDirection;
protected override ListSortDirection SortDirectionCore
{
get
{
return sortDirection;
}
}

private PropertyDescriptor sortProperty;
protected override PropertyDescriptor SortPropertyCore
{
get
{
return sortProperty;
}
}

protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
{
sortProperty = prop;
sortDirection = direction;

GenericComparer.SortColumn = prop.Name;
GenericComparer.SortDescending = direction == ListSortDirection.Descending ? true : false;

List<T> list = (List<T>)Items;
list.Sort(delegate(T lhs, T rhs)
{
if (sortProperty != null)
{
object lhsValue = lhs == null ? null :
sortProperty.GetValue(lhs);
object rhsValue = rhs == null ? null :
sortProperty.GetValue(rhs);
int result;
if (lhsValue == null)
{
result = -1;
}
else if (rhsValue == null)
{
result = 1;
}
else
{
result = GenericComparer.Compare(lhs, rhs);
}
return result;
}
else
{
return 0;
}
});
}

protected override void RemoveSortCore()
{
sortDirection = ListSortDirection.Ascending;
sortProperty = null;
}
}

EDIT This should provide some information about how to create your own IComparer based on my interface above. The advantage to having your own IComparer based on the interface is that you can sort some columns one way, and other columns another (some columns might be strings, and some ints, some might have special rules about what goes on top, etc). Here is an example of how your IComparer might work:

public class MyGenericComparer : IByColumnComparer
{
private string columnToCompare;
private bool descending;

public string SortColumn
{
get { return columnToCompare; }
set { columnToCompare = value; }
}

public bool SortDescending
{
get { return descending; }
set { descending = value; }
}

public MyGenericComparer(string column, bool descend)
{
columnToCompare = column;
descending = descend;
}

public int Compare(object x, object y)
{
MyGenericObject firstObj = (MyGenericObject )x;
MyGenericObject secondObj = (MyGenericObject )y;

if (descending)
{
MyGenericObject tmp = secondObj ;
secondObj = firstObj ;
firstObj = tmp;
}

if (columnToCompare == "StringColumn")
{
//Run code to compare strings, return the appropriate int
//eg, "1" if firstObj was greater, "-1" is secondObj, "0" if equal
}

if (columnToCompare == "IntColumn")
{
//Run code to compare ints, return the appropriate int
//eg, "1" if firstObj was greater, "-1" is secondObj, "0" if equal
}
}
}

Then all you have to do is create your list with an instance of your comparer!

public static MyGenericComparer GridComparer = new MyGenericComparer();
public static SortableGenericCollection<GenericObject> GridList = new SortableGenericCollection<GenericObject>(GridComparer);

How can I natural string sort a datagridview that is databound to a datatable

API declaration:

Imports System.Runtime.InteropServices

Public Module NativeMethods

<DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
Public Function StrCmpLogicalW(x As String, y As String) As Integer
End Function

End Module

Custom comparer:

Public Class NaturalStringComparer
Implements IComparer(Of String)

Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
Return NativeMethods.StrCmpLogicalW(x, y)
End Function

End Class

The following test code requires a form with a DataGridView and a BindingSource with default names:

Public Class Form1

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Create standard table.
Dim table As New DataTable

With table.Columns
.Add("Id", GetType(Integer))
.Add("Code", GetType(String))
End With

With table.Rows
.Add(1, "XAB-1")
.Add(2, "XAB-2")
.Add(3, "XAB-11")
.Add(4, "XAB-3")
.Add(5, "XAB-1A")
.Add(6, "XAB-10")
.Add(7, "XAB-1B")
End With

'Add order column.
table.Columns.Add("Order", GetType(Integer))

'Set the row order.
OrderTableRows(table, "Code", "Order")

'Bind and display in appropriate sort order.
BindingSource1.DataSource = table
BindingSource1.Sort = "Order"
DataGridView1.DataSource = BindingSource1
End Sub

Private Sub OrderTableRows(table As DataTable, sortColumnName As String, orderColumnName As String)
Dim rows = table.Rows.Cast(Of DataRow)().ToArray()

'Get the value to sort by for each row.
Dim sortValues = Array.ConvertAll(rows, Function(row) row.Field(Of String)(sortColumnName))

'Sort the rows by the sort values using a natural comparison.
Array.Sort(sortValues, rows, New NaturalStringComparer)

'Number the rows sequentially based on the sort order.
For i = 0 To rows.GetUpperBound(0)
rows(i)(orderColumnName) = i
Next
End Sub

End Class

That will display the records in the order you want. If you ever make any changes to the Code column, i.e. edit an existing row or add a new row, then you would need to call OrderTableRows again and the data would resort correctly.

In a real app, you may want to not display that Order column, which you can do by explicitly hiding it or else add your grid columns in the designer and omit that one, then set AutoGenerateColumns to False in code. If you want to be able to click a grid column header to sort then you will need to set the SortMode to Programmatic and then use this sorting method behind the scenes.

EDIT:

I have extended the example above to enable sorting when clicking the Code column header cell. Firstly, I added Id and Code columns in the designer. Here's the code that that generated:

'
'idColumn
'
Me.idColumn.DataPropertyName = "Id"
Me.idColumn.HeaderText = "Id"
Me.idColumn.Name = "idColumn"
'
'codeColumn
'
Me.codeColumn.DataPropertyName = "Code"
Me.codeColumn.HeaderText = "Code"
Me.codeColumn.Name = "codeColumn"
Me.codeColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic

You can add the columns in the designer and then set those properties accordingly. I then disabled automatic generation of columns, so that no grid column would be generated for the Order table column:

'Bind and display in appropriate sort order.
BindingSource1.DataSource = table
BindingSource1.Sort = "Order"
DataGridView1.AutoGenerateColumns = False
DataGridView1.DataSource = BindingSource1

Finally, I detected clicks on the Code column header and sorted the BindingSource by the Order column. If sorting was currently by a different column, I sorted in ascending order, otherwise I switched the direction:

Private Sub DataGridView1_ColumnHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles DataGridView1.ColumnHeaderMouseClick
Dim column = DataGridView1.Columns(NameOf(codeColumn))

If e.ColumnIndex = column.Index Then
'Sort by Order as a proxy for Code. Use ascending order by default.
Dim sort = "Order"
Dim direction = SortOrder.Ascending

If DataGridView1.SortedColumn Is Nothing AndAlso
BindingSource1.Sort?.StartsWith("Order", StringComparison.InvariantCultureIgnoreCase) AndAlso
Not BindingSource1.Sort?.EndsWith("DESC", StringComparison.InvariantCultureIgnoreCase) Then
'Already sorted in ascending direction by Order as a proxy for Code so reverse direction.
sort &= " DESC"
direction = SortOrder.Descending
End If

BindingSource1.Sort = sort
column.HeaderCell.SortGlyphDirection = direction
End If
End Sub

To the user, it looks just like there is no Order column and that the Code column is automatically sorted naturally.

DataGridView (partially) databound custom sort

Add the columns in your table instead of your datagridview.
This will make the column databound instead of creating it "manually".
Then to turn the column into a combobox column have a look at this SO link:
How to change a datagridview cell style from the default textbox to combobox in vb.net?

Custom sorting order - DataGridView

Custom sorting an unbound DataGridview

Not sure about your data, but taking them literally this will do the job for an unbound DataGridView DGV:

First you need to hook up a SortCompare handler, maybe like this

 DGV.SortCompare += new DataGridViewSortCompareEventHandler(  this.DGV_SortCompare);

If necessary you can call it on your column (or let the Header click do the job):

 DGV.Sort(DGV.Columns[yourColumn], ListSortDirection.Ascending);

This is the SortCompare event code. It uses simple string manipulation to create a sortable version by padding the last part with zeroes.

 private void DGV_SortCompare(object sender, DataGridViewSortCompareEventArgs e)
{
string s1 = e.CellValue1.ToString().Substring(0, 6) +
e.CellValue1.ToString().Substring(6).PadLeft(5, '0');
string s2 = e.CellValue2.ToString().Substring(0, 6) +
e.CellValue2.ToString().Substring(6).PadLeft(5, '0');
e.SortResult = s1.CompareTo(s2);
e.Handled = true;
}

There is a comprehensive discussion of three methods to sort a DGV here on MSDN. - Clearly this is the easiest one for your problem. Also rather flexible: You can use the e.columnIndex parameter to create spearate comparison strings for other columns as well..

If other columns need no special sorting code you should insert this line to the beginning of the SortCompare:

  if (e.Column.Index != yourColumn) return;

Custom sorting a data bound DataGridView

Update: Since you have changed your question to a DataBound DGV, here is a similar solution for this case:

BindingSource BS = new BindingSource();

private void sortButton_Click(object sender, EventArgs e)
{
DT.Columns.Add("TempSort");
foreach (DataRow row in DT.Rows)
{
string val = row[yourcolumn].ToString();
row["TempSort"] = val.ToString().Substring(0, 6) +
val.ToString().Substring(6).PadLeft(5, '0');
}
BS.DataSource = DT;
BS.Sort = "TempSort ASC";
DT.Columns.Remove("TempSort");
DGV.DataSource = BS;
}

This solution assumes your DataSource is a DataTable DT and will create a temporary column called "TempSort"`and fills it with the prepared version of the data values; it will sort ascending.

For the sorting we use a BindingSource.

To control the right column (here called 'yourcolumn') dynamically as well as the sort order, you will have to write some code yourself, responding to the ColumnHeaderClick...

IComparer sort on databound Datagridview

As far as I know, databound DatagridView cannot be sorted using the methods shown on the linked article.

What you want to do is sort the underlying container. If you're using a DataTable, you can't sort it directly.

What you can do is use Linq's OrderBy and pass it your custom comparer. After that, call AsDataView() on your linq query and bind your DataGridView to this DataView.

Something like this:

 RowComparer comp = new RowComparer();
var query = northwindDataSet.Customers.AsEnumerable().OrderBy(q => q, comp);
DataView dv = query.AsDataView();

customersBindingSource.DataSource = dv;

Note that I'm using a DataTable and a BindingSource in this example, but you should get the idea :-)

Hope this helps

How to sort a Column in DataGridView that is bound to a List in WinForm?

you Can try this......

Complete code to sort the column of datagridview whose datasource is a generic List

//-----------------------------------------------------------------------------------------
//In the form - In constructor or form load, populate the grid.
//--------------------------------------------------------------------------------------------

    List<student> students;

private void PopulateList()
{
student std1 = new student("sss", 15, "Female");
student std2 = new student("ddd", 12, "Male");
student std3 = new student("zzz", 16, "Male");
student std4 = new student("qqq", 14, "Female");
student std5 = new student("aaa", 11, "Male");
student std6 = new student("lll", 13, "Female");

students = new List<student>();
students.Add(std1);
students.Add(std2);
students.Add(std3);
students.Add(std4);
students.Add(std5);
students.Add(std6);

dataGridView1.DataSource = students;
}

//---------------------------------------------------------------------------------------------

//Comparer class to perform sorting based on column name and sort order
//---------------------------------------------------------------------------------------------

class StudentComparer : IComparer<Student>
{
string memberName = string.Empty; // specifies the member name to be sorted
SortOrder sortOrder = SortOrder.None; // Specifies the SortOrder.

/// <summary>
/// constructor to set the sort column and sort order.
/// </summary>
/// <param name="strMemberName"></param>
/// <param name="sortingOrder"></param>
public StudentComparer(string strMemberName, SortOrder sortingOrder)
{
memberName = strMemberName;
sortOrder = sortingOrder;
}

/// <summary>
/// Compares two Students based on member name and sort order
/// and return the result.
/// </summary>
/// <param name="Student1"></param>
/// <param name="Student2"></param>
/// <returns></returns>
public int Compare(Student Student1, Student Student2)
{
int returnValue = 1;
switch (memberName)
{
case "Name" :
if (sortOrder == SortOrder.Ascending)
{
returnValue = Student1.Name.CompareTo(Student2.Name);
}
else
{
returnValue = Student2.Name.CompareTo(Student1.Name);
}

break;
case "Sex":
if (sortOrder == SortOrder.Ascending)
{
returnValue = Student1.Sex.CompareTo(Student2.Sex);
}
else
{
returnValue = Student2.Sex.CompareTo(Student1.Sex);
}
break;
default:
if (sortOrder == SortOrder.Ascending)
{
returnValue = Student1.Name.CompareTo(Student2.Name);
}
else
{
returnValue = Student2.Name.CompareTo(Student1.StudentId);
}
break;
}
return returnValue;
}
}

//---------------------------------------------------------------------------------------------

// Performing sort on click on Column Header
//---------------------------------------------------------------------------------------------

    private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
//get the current column details
string strColumnName = dataGridView1.Columns[e.ColumnIndex].Name;
SortOrder strSortOrder = getSortOrder(e.ColumnIndex);

students.Sort(new StudentComparer(strColumnName, strSortOrder));
dataGridView1.DataSource = null;
dataGridView1.DataSource = students;
customizeDataGridView();
dataGridView1.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = strSortOrder;
}

/// <summary>
/// Get the current sort order of the column and return it
/// set the new SortOrder to the columns.
/// </summary>
/// <param name="columnIndex"></param>
/// <returns>SortOrder of the current column</returns>
private SortOrder getSortOrder(int columnIndex)
{
if (dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.None ||
dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.Descending)
{
dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
return SortOrder.Ascending;
}
else
{
dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Descending;
return SortOrder.Descending;
}
}

I am hoping that it will helps you.....



Related Topics



Leave a reply



Submit