Selected rows when sorting DataGridView in WinForm application

Here is my approach in VB.NET

Private cSelectedRow As String

Private Sub DataGridView1_CellMouseDown(sender As Object, e As DataGridViewCellMouseEventArgs) Handles DataGridView1.CellMouseDown
    If e.RowIndex = -1 AndAlso DataGridView1.SelectedRows.Count > 0 Then
        cSelectedRow = DataGridView1.SelectedRows(0).Cells("ID").Value.ToString()
    End If
End Sub

I used the same events as David Hall, but did not use the BindingSource. So I loop through all Lines of the grid to find the one that was selected before.

Private Sub DataGridView1_Sorted() Handles DataGridView1.Sorted
    DataGridView1.ClearSelection()
    For Each xRow As DataGridViewRow In DataGridView1.Rows
        If xRow.Cells("ID").Value = cSelectedRow Then
            DataGridView1.CurrentCell = xRow.Cells(0)
            'Line Found. No need to loop through the rest.
            Exit For
        End If
    Next
End Sub

You can work around this behaviour by storing away the value of the currently selected row (or rows) before sorting and then reselecting the row afterwards.

You need to use the CellMouseDown event - it is necessary to use this event since it is the only one which fires before the sort happens. Alternative events like ColumnHeaderMouseClick are all too late.

In the CellMouseDown eventhandler check that the row index is -1 to ensure that the headers were selected.

void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.RowIndex == -1)
    {
        selected = dataGridView1.SelectedRows[0].Cells[0].Value.ToString();
    }
}

I have a class level field selected that I use to store the unique identifier of the column that is selected. If you don't have a unique id then you could add in a column for this purpose and hide it.

Then in the Sorted eventhandler of the DataGridView you can use the .Find() method of the grid's binding source:

void dataGridView1_Sorted(object sender, EventArgs e)
{
    if (!string.IsNullOrEmpty(selected))
    {
        int itemFound = _bindingSource.Find("name", selected);
        _bindingSource.Position = itemFound;
    }
}

While investigating this I found the following post on the MSDN forums where the answer uses the DataBindingComplete event - I'm not 100% why they found that necessary as my approach has worked for all my tests, but you might find it a helpful reference.


I tried David Hall's answer but did not worked to me, on one of the three states of sorting operation in my grid, so I've changed something in my MyDataGridView.cs class, as this:

public string SelectedValue { get; set; }
private bool headerFirstClick = true;
public bool HeaderWasClicked = false;

private void MyDataGridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
    try
       {
            if (headerFirstClick) Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = SortOrder.None;
            if (Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection == SortOrder.None)
            {
                Columns[e.ColumnIndex].SortMode = DataGridViewColumnSortMode.Automatic;
                Sort(Columns[e.ColumnIndex], System.ComponentModel.ListSortDirection.Ascending);
                Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
            }
                else if (Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection == SortOrder.Ascending)
            {
                Columns[e.ColumnIndex].SortMode = DataGridViewColumnSortMode.Programmatic;
                ((BindingSource)DataSource).Sort = string.Empty;
                Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = SortOrder.None;
            }
            headerFirstClick = false;
            int findValue = ((BindingSource)DataSource).Find(Columns[e.ColumnIndex].Name, SelectedValue);
            ((BindingSource)DataSource).Position = findValue;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

protected override void OnCellClick(DataGridViewCellEventArgs e)
    {
        base.OnCellClick(e);
        if (e != null)
        {
            try
            {
                HeaderWasClicked = (e.RowIndex == -1);
                SelectedValue = SelectedRows[0].Cells[e.ColumnIndex].Value.ToString();
            }
            catch(Exception){}
        }
    }