[asp.net] GridView sorting: SortDirection always Ascending

I have a gridview and I need to sort its elements when the user clicks on the header.
Its datasource is a List object.

The aspx is defined this way :

<asp:GridView ID="grdHeader" AllowSorting="true" AllowPaging="false" 
    AutoGenerateColumns="false" Width="780" runat="server"  OnSorting="grdHeader_OnSorting" EnableViewState="true">
    <Columns>
        <asp:BoundField DataField="Entitycode" HeaderText="Entity" SortExpression="Entitycode" />
        <asp:BoundField DataField="Statusname" HeaderText="Status" SortExpression="Statusname" />
        <asp:BoundField DataField="Username" HeaderText="User" SortExpression="Username" />
    </Columns>
</asp:GridView>

The code behind is defined this way :
First load :

protected void btnSearch_Click(object sender, EventArgs e)
{
    List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
    this.grdHeader.DataSource = items;
    this.grdHeader.DataBind();
}

when the user clicks on headers :

protected void grdHeader_OnSorting(object sender, GridViewSortEventArgs e)
{
    List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
    items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, e.SortDirection));
    grdHeader.DataSource = items;
    grdHeader.DataBind();
}

My problem is that e.SortDirection is always set to Ascending.
I have webpage with a similar code and it works well, e.SortDirection alternates between Ascending and Descending.

What did I do wrong ?

This question is related to asp.net sorting gridview

The answer is


Automatic bidirectional sorting only works with the SQL data source. Unfortunately, all the documentation in MSDN assumes you are using that, so GridView can get a bit frustrating.

The way I do it is by keeping track of the order on my own. For example:

    protected void OnSortingResults(object sender, GridViewSortEventArgs e)
    {
        // If we're toggling sort on the same column, we simply toggle the direction. Otherwise, ASC it is.
        // e.SortDirection is useless and unreliable (only works with SQL data source).
        if (_sortBy == e.SortExpression)
            _sortDirection = _sortDirection == SortDirection.Descending ? SortDirection.Ascending : SortDirection.Descending;
        else
            _sortDirection = SortDirection.Ascending;

        _sortBy = e.SortExpression;

        BindResults();
    }

I don't know why everyone forgets about using hidden fields! They are so much "cheaper" than ViewState (which I have turned off since 2005). If you don't want to use Session or ViewState, then here is my solution:

Put these two hidden fields on your aspx page, and put the default sort you want for your data (I'm using LastName for example):

<asp:HiddenField ID="hfSortExpression" runat="server" Value="LastName" />
<asp:HiddenField ID="hfSortDirection" runat="server" Value="Ascending" />

Then put this helper code in your Base page (you have a base page don't you? If not, put in your .cs code behind).

/// <summary>
/// Since native ASP.Net GridViews do not provide accurate SortDirections, 
/// we must save a hidden field with previous sort Direction and Expression.
/// Put these two hidden fields on page and call this method in grid sorting event
/// </summary>
/// <param name="hfSortExpression">The hidden field on page that has the PREVIOUS column that is sorted on</param>
/// <param name="hfSortDirection">The hidden field on page that has the PREVIOUS sort direction</param>
protected SortDirection GetSortDirection(GridViewSortEventArgs e, HiddenField hfSortExpression, HiddenField hfSortDirection)
{
    //assume Ascending always by default!!
    SortDirection sortDirection = SortDirection.Ascending;

    //see what previous column (if any) was sorted on
    string previousSortExpression = hfSortExpression.Value;
    //see what previous sort direction was used
    SortDirection previousSortDirection = !string.IsNullOrEmpty(hfSortDirection.Value) ? ((SortDirection)Enum.Parse(typeof(SortDirection), hfSortDirection.Value)) : SortDirection.Ascending;

    //check if we are now sorting on same column
    if (e.SortExpression == previousSortExpression)
    {
        //check if previous direction was ascending
        if (previousSortDirection == SortDirection.Ascending)
        {
            //since column name matches but direction doesn't, 
            sortDirection = SortDirection.Descending;
        }
    }

    // save them back so you know for next time
    hfSortExpression.Value = e.SortExpression;
    hfSortDirection.Value = sortDirection.ToString();

    return sortDirection;
}

Next, you need to handle the sorting in your grid sorting event handler. Call the method above from the sorting event handler, before calling your main method that gets your data

protected void gridContacts_Sorting(object sender, GridViewSortEventArgs e)
{
    //get the sort direction (since GridView sortDirection is not implemented!)
    SortDirection sortDirection = GetSortDirection(e, hfSortExpression, hfSortDirection);

    //get data, sort and rebind (obviously, this is my own method... you must replace with your own)
    GetCases(_accountId, e.SortExpression, sortDirection);
}

Since so many examples out there use DataTables or DataViews or other non LINQ friendly collections, I thought I'd include an example a call to a middle tier method that returns a generic list, and use LINQ to do the sorting in order to round out the example and make it more "real world":

private void GetCases(AccountID accountId, string sortExpression, SortDirection sortDirection)
{
    //get some data from a middle tier method (database etc._)(
    List<PendingCase> pendingCases = MyMiddleTier.GetCasesPending(accountId.Value);
    //show a count to the users on page (this is just nice to have)
    lblCountPendingCases.Text = pendingCases.Count.ToString();
    //do the actual sorting of your generic list of custom objects
    pendingCases = Sort(sortExpression, sortDirection, pendingCases);
    //bind your grid
    grid.DataSource = pendingCases;
    grid.DataBind();
}

Lastly, here is the down and dirty sorting using LINQ on a generic list of custom objects. I'm sure there is something fancier out there that will do the trick, but this illustrates the concept:

private static List Sort(string sortExpression, SortDirection sortDirection, List pendingCases) {

    switch (sortExpression)
    {
        case "FirstName":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.FirstName).ToList() : pendingCases.OrderByDescending(c => c.FirstName).ToList();
            break;
        case "LastName":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.LastName).ToList() : pendingCases.OrderByDescending(c => c.LastName).ToList();
            break;
        case "Title":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.Title).ToList() : pendingCases.OrderByDescending(c => c.Title).ToList();
            break;
        case "AccountName":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.AccountName).ToList() : pendingCases.OrderByDescending(c => c.AccountName).ToList();
            break;
        case "CreatedByEmail":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.CreatedByEmail).ToList() : pendingCases.OrderByDescending(c => c.CreatedByEmail).ToList();
            break;
        default:
            break;
    }
    return pendingCases;
}

Last but not least (did I say that already?) you may want to put something like this in your Page_Load handler, so that the grid binds by default upon page load... Note that _accountId is a querystring parameter, converted to a custom type of AccountID of my own in this case...

    if (!Page.IsPostBack)
    {
        //sort by LastName ascending by default
        GetCases(_accountId,hfSortExpression.Value,SortDirection.Ascending);
    }

In vb.net but very simple!

Protected Sub grTicketHistory_Sorting(sender As Object, e As GridViewSortEventArgs) Handles grTicketHistory.Sorting

    Dim dt As DataTable = Session("historytable")
    If Session("SortDirection" & e.SortExpression) = "ASC" Then
        Session("SortDirection" & e.SortExpression) = "DESC"
    Else
        Session("SortDirection" & e.SortExpression) = "ASC"
    End If
    dt.DefaultView.Sort = e.SortExpression & " " & Session("SortDirection" & e.SortExpression)
    grTicketHistory.DataSource = dt
    grTicketHistory.DataBind()

End Sub

It's been awhile since I used a GridView, but I think you need to set the grid's SortDirection property to whatever it currently is before leaving the OnSorting method.

So....

List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, e.SortDirection));
grdHeader.SortDirection = e.SortDirection.Equals(SortDirection.Ascending) ? SortDirection.Descending : SortDirection.Ascending;
grdHeader.DataSource = items;
grdHeader.DataBind();


This problem is absent not only with SQL data sources but with Object Data Sources as well. However, when setting the DataSource dynamically in code, that's when this goes bad. Unfortunately, MSDN sometimes is really very poor on information. A simple mentioning of this behavior(this is not a bug but a design issue) would save a lot of time. Anyhow, I'm not very inclined to use Session variables for this. I usually store the sorting direction in a ViewState.


Old string, but maybe my answer will help somebody.

First get your SqlDataSource as a DataView:

Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As DataGridSortCommandEventArgs) Handles grid1.SortCommand
    Dim dataView As DataView = CType(SqlDataSource1.Select(DataSourceSelectArguments.Empty), DataView)
    dataView.Sort = e.SortExpression + dataView.FieldSortDirection(Session, e.SortExpression)

    grid1.DataSourceID = Nothing
    grid1.DataSource = dataView
    grid1.DataBind()

End Sub

Then use an extension method for the sort (kind of a cheep shot, but a good start):

public static class DataViewExtensions
{
    public static string FieldSortDirection(this DataView dataView, HttpSessionState session, string sortExpression)
    {
        const string SORT_DIRECTION = "SortDirection";
        var identifier = SORT_DIRECTION + sortExpression;

        if (session[identifier] != null)
        {
            if ((string) session[identifier] == " ASC")
                session[identifier] = " DESC";
            else if ((string) session[identifier] == " DESC")
                session[identifier] = " ASC";
        }
        else
            session[identifier] = " ASC";

        return (string) session[identifier];
    }
}

All that answer not fully correct. I use That:

protected void SetPageSort(GridViewSortEventArgs e) 
        { 
            if (e.SortExpression == SortExpression) 
            { 
                if (SortDirection == "ASC") 
                { 
                    SortDirection = "DESC"; 
                } 
                else 
                { 
                    SortDirection = "ASC"; 
                } 
            } 
            else 
            {
                if (SortDirection == "ASC")
                {
                    SortDirection = "DESC";
                }
                else
                {
                    SortDirection = "ASC";
                } 
                SortExpression = e.SortExpression; 
            } 
        } 
  protected void gridView_Sorting(object sender, GridViewSortEventArgs e)
        {
            SetPageSort(e); 

in gridView_Sorting...


Another one :) Don't need to hard code column names..

DataTable dt = GetData();

    SortDirection sd;
    string f;
    GridViewSortDirection(gvProductBreakdown, e, out sd, out f);
    dt.DefaultView.Sort = sd == SortDirection.Ascending ? f + " asc" : f + " desc";
    gvProductBreakdown.DataSource = dt;
    gvProductBreakdown.DataBind();

Ant then:

 private void GridViewSortDirection(GridView g, GridViewSortEventArgs e, out SortDirection d, out string f)
    {
        f = e.SortExpression;
        d = e.SortDirection;
 if (g.Attributes[f] != null)
        {
            d = g.Attributes[f] == "ASC" ? SortDirection.Descending : SortDirection.Ascending;

            g.Attributes[f] = d == SortDirection.Ascending ? "ASC" : "DESC";
        }
        else
        {
            g.Attributes[f] = "ASC";
            d = SortDirection.Ascending;
        }

The way I did this is similar to the code that the accepted answer provided, bit is a bit different so I thought I would put it out there as well. Note that this sorting is being done to a DataTable before it is being bound to the GridView.DataSource.

Option One: Using ViewState

void DataGrid_Sorting(object sender, GridViewSortEventArgs e)
{
    if (e.SortExpression == (string)ViewState["SortColumn"])
    {
        // We are resorting the same column, so flip the sort direction
        e.SortDirection = 
            ((SortDirection)ViewState["SortColumnDirection"] == SortDirection.Ascending) ? 
            SortDirection.Descending : SortDirection.Ascending;
    }
    // Apply the sort
    this._data.DefaultView.Sort = e.SortExpression +
        (string)((e.SortDirection == SortDirection.Ascending) ? " ASC" : " DESC");
    ViewState["SortColumn"] = e.SortExpression;
    ViewState["SortColumnDirection"] = e.SortDirection;
}

Option Two: Using Session

Note that the following is being provided for legacy purposes in the event that you see it in the field, or that you are still supporting company systems that are targeting older browsers.

void DataGrid_Sorting(object sender, GridViewSortEventArgs e)
{
    if (e.SortExpression == (string)HttpContext.Current.Session["SortColumn"])
    {
        // We are resorting the same column, so flip the sort direction
        e.SortDirection = 
            ((SortDirection)HttpContext.Current.Session["SortColumnDirection"] == SortDirection.Ascending) ? 
            SortDirection.Descending : SortDirection.Ascending;
    }
    // Apply the sort
    this._data.DefaultView.Sort = e.SortExpression +
        (string)((e.SortDirection == SortDirection.Ascending) ? " ASC" : " DESC");
    HttpContext.Current.Session["SortColumn"] = e.SortExpression;
    HttpContext.Current.Session["SortColumnDirection"] = e.SortDirection;
}

Using SecretSquirrel's solution above

here is my full working, production code. Just change dgvCoaches to your grid view name.

... during the binding of the grid

        dgvCoaches.DataSource = dsCoaches.Tables[0];
        ViewState["AllCoaches"] = dsCoaches.Tables[0];
        dgvCoaches.DataBind();

and now the sorting

protected void gridView_Sorting(object sender, GridViewSortEventArgs e)
{
    DataTable dt = ViewState["AllCoaches"] as DataTable;

    if (dt != null)
    {
        if (e.SortExpression == (string)ViewState["SortColumn"])
        {
            // We are resorting the same column, so flip the sort direction
            e.SortDirection =
                ((SortDirection)ViewState["SortColumnDirection"] == SortDirection.Ascending) ?
                SortDirection.Descending : SortDirection.Ascending;
        }
        // Apply the sort
        dt.DefaultView.Sort = e.SortExpression +
            (string)((e.SortDirection == SortDirection.Ascending) ? " ASC" : " DESC");
        ViewState["SortColumn"] = e.SortExpression;
        ViewState["SortColumnDirection"] = e.SortDirection; 

        dgvCoaches.DataSource = dt;
        dgvCoaches.DataBind();
    }
}

and here is the aspx code:

<asp:GridView ID="dgvCoaches" runat="server" 
    CssClass="table table-hover table-striped" GridLines="None"  DataKeyNames="HealthCoachID" OnRowCommand="dgvCoaches_RowCommand"
    AutoGenerateColumns="False" OnSorting="gridView_Sorting" AllowSorting="true">
    <Columns>
        <asp:BoundField DataField="HealthCoachID" Visible="false" />
        <asp:BoundField DataField="LastName" HeaderText="Last Name" SortExpression="LastName" />
        <asp:BoundField DataField="FirstName" HeaderText="First Name" SortExpression="FirstName" />
        <asp:BoundField DataField="LoginName" HeaderText="Login Name" SortExpression="LoginName" />
        <asp:BoundField DataField="Email" HeaderText="Email" SortExpression="Email" HtmlEncode="false" DataFormatString="<a href=mailto:{0}>{0}</a>" />
        <asp:TemplateField>
            <ItemTemplate>
                    <asp:LinkButton runat="server" BorderStyle="None" CssClass="btn btn-default" Text="<i class='glyphicon glyphicon-edit'></i>" CommandName="Update" CommandArgument="<%# ((GridViewRow) Container).RowIndex %>" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:LinkButton runat="server" OnClientClick="return ConfirmOnDelete();" BorderStyle="None" CssClass="btn btn-default" Text="<i class='glyphicon glyphicon-remove'></i>" CommandName="Delete" CommandArgument="<%# ((GridViewRow) Container).RowIndex %>" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
    <RowStyle CssClass="cursor-pointer" />
</asp:GridView>

It can be done without the use of View State or Session. Current order can be determined based on value in first and last row in the column we sort by:

        protected void gvItems_Sorting(object sender, GridViewSortEventArgs e)
    {
        GridView grid = sender as GridView; // get reference to grid
        SortDirection currentSortDirection = SortDirection.Ascending; // default order

        // get column index by SortExpression
        int columnIndex = grid.Columns.IndexOf(grid.Columns.OfType<DataControlField>()
                                      .First(x => x.SortExpression == e.SortExpression));

        // sort only if grid has more than 1 row
        if (grid.Rows.Count > 1)
        {
            // get cells
            TableCell firstCell = grid.Rows[0].Cells[columnIndex];
            TableCell lastCell = grid.Rows[grid.Rows.Count - 1].Cells[columnIndex];

            // if field type of the cell is 'TemplateField' Text property is always empty.
            // Below assumes that value is binded to Label control in 'TemplateField'.
            string firstCellValue = firstCell.Controls.Count == 0 ? firstCell.Text : ((Label)firstCell.Controls[1]).Text;
            string lastCellValue = lastCell.Controls.Count == 0 ? lastCell.Text : ((Label)lastCell.Controls[1]).Text;

            DateTime tmpDate;
            decimal tmpDecimal;

            // try to determinate cell type to ensure correct ordering
            // by date or number
            if (DateTime.TryParse(firstCellValue, out tmpDate)) // sort as DateTime
            {
                currentSortDirection = 
                    DateTime.Compare(Convert.ToDateTime(firstCellValue), 
                                     Convert.ToDateTime(lastCellValue)) < 0 ? 
                                            SortDirection.Ascending : SortDirection.Descending;
            }
            else if (Decimal.TryParse(firstCellValue, out tmpDecimal)) // sort as any numeric type
            {
                currentSortDirection = Decimal.Compare(Convert.ToDecimal(firstCellValue), 
                                                       Convert.ToDecimal(lastCellValue)) < 0 ? 
                                                       SortDirection.Ascending : SortDirection.Descending;
            }
            else // sort as string
            {
                currentSortDirection = string.CompareOrdinal(firstCellValue, lastCellValue) < 0 ? 
                                                             SortDirection.Ascending : SortDirection.Descending;
            }
        }

        // then bind GridView using correct sorting direction (in this example I use Linq)
        if (currentSortDirection == SortDirection.Descending)
        {
            grid.DataSource = myItems.OrderBy(x => x.GetType().GetProperty(e.SortExpression).GetValue(x, null));
        }
        else
        {
            grid.DataSource = myItems.OrderByDescending(x => x.GetType().GetProperty(e.SortExpression).GetValue(x, null));
        }

        grid.DataBind();
    }

All that answer not fully correct. I use That:

protected void SetPageSort(GridViewSortEventArgs e) 
        { 
            if (e.SortExpression == SortExpression) 
            { 
                if (SortDirection == "ASC") 
                { 
                    SortDirection = "DESC"; 
                } 
                else 
                { 
                    SortDirection = "ASC"; 
                } 
            } 
            else 
            {
                if (SortDirection == "ASC")
                {
                    SortDirection = "DESC";
                }
                else
                {
                    SortDirection = "ASC";
                } 
                SortExpression = e.SortExpression; 
            } 
        } 
  protected void gridView_Sorting(object sender, GridViewSortEventArgs e)
        {
            SetPageSort(e); 

in gridView_Sorting...


It can be done without the use of View State or Session. Current order can be determined based on value in first and last row in the column we sort by:

        protected void gvItems_Sorting(object sender, GridViewSortEventArgs e)
    {
        GridView grid = sender as GridView; // get reference to grid
        SortDirection currentSortDirection = SortDirection.Ascending; // default order

        // get column index by SortExpression
        int columnIndex = grid.Columns.IndexOf(grid.Columns.OfType<DataControlField>()
                                      .First(x => x.SortExpression == e.SortExpression));

        // sort only if grid has more than 1 row
        if (grid.Rows.Count > 1)
        {
            // get cells
            TableCell firstCell = grid.Rows[0].Cells[columnIndex];
            TableCell lastCell = grid.Rows[grid.Rows.Count - 1].Cells[columnIndex];

            // if field type of the cell is 'TemplateField' Text property is always empty.
            // Below assumes that value is binded to Label control in 'TemplateField'.
            string firstCellValue = firstCell.Controls.Count == 0 ? firstCell.Text : ((Label)firstCell.Controls[1]).Text;
            string lastCellValue = lastCell.Controls.Count == 0 ? lastCell.Text : ((Label)lastCell.Controls[1]).Text;

            DateTime tmpDate;
            decimal tmpDecimal;

            // try to determinate cell type to ensure correct ordering
            // by date or number
            if (DateTime.TryParse(firstCellValue, out tmpDate)) // sort as DateTime
            {
                currentSortDirection = 
                    DateTime.Compare(Convert.ToDateTime(firstCellValue), 
                                     Convert.ToDateTime(lastCellValue)) < 0 ? 
                                            SortDirection.Ascending : SortDirection.Descending;
            }
            else if (Decimal.TryParse(firstCellValue, out tmpDecimal)) // sort as any numeric type
            {
                currentSortDirection = Decimal.Compare(Convert.ToDecimal(firstCellValue), 
                                                       Convert.ToDecimal(lastCellValue)) < 0 ? 
                                                       SortDirection.Ascending : SortDirection.Descending;
            }
            else // sort as string
            {
                currentSortDirection = string.CompareOrdinal(firstCellValue, lastCellValue) < 0 ? 
                                                             SortDirection.Ascending : SortDirection.Descending;
            }
        }

        // then bind GridView using correct sorting direction (in this example I use Linq)
        if (currentSortDirection == SortDirection.Descending)
        {
            grid.DataSource = myItems.OrderBy(x => x.GetType().GetProperty(e.SortExpression).GetValue(x, null));
        }
        else
        {
            grid.DataSource = myItems.OrderByDescending(x => x.GetType().GetProperty(e.SortExpression).GetValue(x, null));
        }

        grid.DataBind();
    }

A simple solution:

protected SortDirection GetSortDirection(string column)
{
    SortDirection nextDir = SortDirection.Ascending; // Default next sort expression behaviour.
    if (ViewState["sort"] != null && ViewState["sort"].ToString() == column)
    {   // Exists... DESC.
        nextDir = SortDirection.Descending;
        ViewState["sort"] = null;
    }
    else
    {   // Doesn't exists, set ViewState.
        ViewState["sort"] = column;
    }
    return nextDir;
}

Much like the default GridView sorting and lightweight on the ViewState.

USAGE:

protected void grdHeader_OnSorting(object sender, GridViewSortEventArgs e)
{
    List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();

    items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, GetSortDirection(e.SortExpression));
    grdHeader.DataSource = items;
    grdHeader.DataBind();
}

This problem is absent not only with SQL data sources but with Object Data Sources as well. However, when setting the DataSource dynamically in code, that's when this goes bad. Unfortunately, MSDN sometimes is really very poor on information. A simple mentioning of this behavior(this is not a bug but a design issue) would save a lot of time. Anyhow, I'm not very inclined to use Session variables for this. I usually store the sorting direction in a ViewState.


To toggle ascending and descending, I use a method in my app's BasePage to cache the sort expression and sort direction:

protected void SetPageSort(GridViewSortEventArgs e)
{
    if (e.SortExpression == SortExpression)
    {
        if (SortDirection == "ASC")
        {
            SortDirection = "DESC";
        }
        else
        {
            SortDirection = "ASC";
        }
    }
    else
    {
        SortDirection = "ASC";
        SortExpression = e.SortExpression;
    }
}

SortExpression and SortDirection are both properties in BasePage that store and retrieve their values from ViewState.

So all of my derived pages just call SetPageSort from the GridView's Sorting method, and bind the GridView:

protected void gv_Sorting(object sender, GridViewSortEventArgs e)
{
    SetPageSort(e);
    BindGrid();
}

BindGrid checks the SortExpression and uses it and SortDirection to do an ORDERY BY on the grid's data source, something like this:

if (SortExpression.Length > 0)
{
    qry.ORDER_BY(SortExpression + " " + SortDirection);
}

gv.DataSource = qry.ExecuteReader();
gv.DataBind();

So, the base class' SetPageSort removes much of the drudgery of GridView sorting. I feel like I'm forgetting something, but that's the general idea.


Wrote this, it works for me:

 protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
    {
        if (ViewState["sortExpression"] == null || ViewState["sortExpression"].ToString() != e.SortExpression.ToString())
            MyDataTable.DefaultView.Sort = e.SortExpression + " ASC";
        else
        {
            if (ViewState["SortDirection"].ToString() == "Ascending")
                MyDataTable.DefaultView.Sort = e.SortExpression = e.SortExpression + " DESC";
            else
                MyDataTable.DefaultView.Sort = e.SortExpression + " ASC";
        }

        GridView1.DataSource = MyDataTable;
        GridView1.DataBind();

        ViewState["sortExpression"] = e.SortExpression;
        ViewState["SortDirection"] = e.SortDirection;
    }

It's been awhile since I used a GridView, but I think you need to set the grid's SortDirection property to whatever it currently is before leaving the OnSorting method.

So....

List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, e.SortDirection));
grdHeader.SortDirection = e.SortDirection.Equals(SortDirection.Ascending) ? SortDirection.Descending : SortDirection.Ascending;
grdHeader.DataSource = items;
grdHeader.DataBind();


I don't know why everyone forgets about using hidden fields! They are so much "cheaper" than ViewState (which I have turned off since 2005). If you don't want to use Session or ViewState, then here is my solution:

Put these two hidden fields on your aspx page, and put the default sort you want for your data (I'm using LastName for example):

<asp:HiddenField ID="hfSortExpression" runat="server" Value="LastName" />
<asp:HiddenField ID="hfSortDirection" runat="server" Value="Ascending" />

Then put this helper code in your Base page (you have a base page don't you? If not, put in your .cs code behind).

/// <summary>
/// Since native ASP.Net GridViews do not provide accurate SortDirections, 
/// we must save a hidden field with previous sort Direction and Expression.
/// Put these two hidden fields on page and call this method in grid sorting event
/// </summary>
/// <param name="hfSortExpression">The hidden field on page that has the PREVIOUS column that is sorted on</param>
/// <param name="hfSortDirection">The hidden field on page that has the PREVIOUS sort direction</param>
protected SortDirection GetSortDirection(GridViewSortEventArgs e, HiddenField hfSortExpression, HiddenField hfSortDirection)
{
    //assume Ascending always by default!!
    SortDirection sortDirection = SortDirection.Ascending;

    //see what previous column (if any) was sorted on
    string previousSortExpression = hfSortExpression.Value;
    //see what previous sort direction was used
    SortDirection previousSortDirection = !string.IsNullOrEmpty(hfSortDirection.Value) ? ((SortDirection)Enum.Parse(typeof(SortDirection), hfSortDirection.Value)) : SortDirection.Ascending;

    //check if we are now sorting on same column
    if (e.SortExpression == previousSortExpression)
    {
        //check if previous direction was ascending
        if (previousSortDirection == SortDirection.Ascending)
        {
            //since column name matches but direction doesn't, 
            sortDirection = SortDirection.Descending;
        }
    }

    // save them back so you know for next time
    hfSortExpression.Value = e.SortExpression;
    hfSortDirection.Value = sortDirection.ToString();

    return sortDirection;
}

Next, you need to handle the sorting in your grid sorting event handler. Call the method above from the sorting event handler, before calling your main method that gets your data

protected void gridContacts_Sorting(object sender, GridViewSortEventArgs e)
{
    //get the sort direction (since GridView sortDirection is not implemented!)
    SortDirection sortDirection = GetSortDirection(e, hfSortExpression, hfSortDirection);

    //get data, sort and rebind (obviously, this is my own method... you must replace with your own)
    GetCases(_accountId, e.SortExpression, sortDirection);
}

Since so many examples out there use DataTables or DataViews or other non LINQ friendly collections, I thought I'd include an example a call to a middle tier method that returns a generic list, and use LINQ to do the sorting in order to round out the example and make it more "real world":

private void GetCases(AccountID accountId, string sortExpression, SortDirection sortDirection)
{
    //get some data from a middle tier method (database etc._)(
    List<PendingCase> pendingCases = MyMiddleTier.GetCasesPending(accountId.Value);
    //show a count to the users on page (this is just nice to have)
    lblCountPendingCases.Text = pendingCases.Count.ToString();
    //do the actual sorting of your generic list of custom objects
    pendingCases = Sort(sortExpression, sortDirection, pendingCases);
    //bind your grid
    grid.DataSource = pendingCases;
    grid.DataBind();
}

Lastly, here is the down and dirty sorting using LINQ on a generic list of custom objects. I'm sure there is something fancier out there that will do the trick, but this illustrates the concept:

private static List Sort(string sortExpression, SortDirection sortDirection, List pendingCases) {

    switch (sortExpression)
    {
        case "FirstName":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.FirstName).ToList() : pendingCases.OrderByDescending(c => c.FirstName).ToList();
            break;
        case "LastName":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.LastName).ToList() : pendingCases.OrderByDescending(c => c.LastName).ToList();
            break;
        case "Title":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.Title).ToList() : pendingCases.OrderByDescending(c => c.Title).ToList();
            break;
        case "AccountName":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.AccountName).ToList() : pendingCases.OrderByDescending(c => c.AccountName).ToList();
            break;
        case "CreatedByEmail":
            pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.CreatedByEmail).ToList() : pendingCases.OrderByDescending(c => c.CreatedByEmail).ToList();
            break;
        default:
            break;
    }
    return pendingCases;
}

Last but not least (did I say that already?) you may want to put something like this in your Page_Load handler, so that the grid binds by default upon page load... Note that _accountId is a querystring parameter, converted to a custom type of AccountID of my own in this case...

    if (!Page.IsPostBack)
    {
        //sort by LastName ascending by default
        GetCases(_accountId,hfSortExpression.Value,SortDirection.Ascending);
    }

I got tired of dealing with this issue and put the sort direction and sort column in the ViewState....


This is another way of solving the issue:

protected void grdHeader_OnSorting(object sender, GridViewSortEventArgs e)
{
    List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
    items.Sort = e.SortExpression + " " + ConvertSortDirectionToSql(e);
    grdHeader.DataSource = items;
    grdHeader.DataBind();
}

private string ConvertSortDirectionToSql(GridViewSortEventArgs e)
{
    ViewState[e.SortExpression] = ViewState[e.SortExpression] ?? "ASC";
    ViewState[e.SortExpression] = (ViewState[e.SortExpression].ToString() == "ASC") ? "DESC" : "ASC";
    return ViewState[e.SortExpression].ToString();
}

To toggle ascending and descending, I use a method in my app's BasePage to cache the sort expression and sort direction:

protected void SetPageSort(GridViewSortEventArgs e)
{
    if (e.SortExpression == SortExpression)
    {
        if (SortDirection == "ASC")
        {
            SortDirection = "DESC";
        }
        else
        {
            SortDirection = "ASC";
        }
    }
    else
    {
        SortDirection = "ASC";
        SortExpression = e.SortExpression;
    }
}

SortExpression and SortDirection are both properties in BasePage that store and retrieve their values from ViewState.

So all of my derived pages just call SetPageSort from the GridView's Sorting method, and bind the GridView:

protected void gv_Sorting(object sender, GridViewSortEventArgs e)
{
    SetPageSort(e);
    BindGrid();
}

BindGrid checks the SortExpression and uses it and SortDirection to do an ORDERY BY on the grid's data source, something like this:

if (SortExpression.Length > 0)
{
    qry.ORDER_BY(SortExpression + " " + SortDirection);
}

gv.DataSource = qry.ExecuteReader();
gv.DataBind();

So, the base class' SetPageSort removes much of the drudgery of GridView sorting. I feel like I'm forgetting something, but that's the general idea.


Using SecretSquirrel's solution above

here is my full working, production code. Just change dgvCoaches to your grid view name.

... during the binding of the grid

        dgvCoaches.DataSource = dsCoaches.Tables[0];
        ViewState["AllCoaches"] = dsCoaches.Tables[0];
        dgvCoaches.DataBind();

and now the sorting

protected void gridView_Sorting(object sender, GridViewSortEventArgs e)
{
    DataTable dt = ViewState["AllCoaches"] as DataTable;

    if (dt != null)
    {
        if (e.SortExpression == (string)ViewState["SortColumn"])
        {
            // We are resorting the same column, so flip the sort direction
            e.SortDirection =
                ((SortDirection)ViewState["SortColumnDirection"] == SortDirection.Ascending) ?
                SortDirection.Descending : SortDirection.Ascending;
        }
        // Apply the sort
        dt.DefaultView.Sort = e.SortExpression +
            (string)((e.SortDirection == SortDirection.Ascending) ? " ASC" : " DESC");
        ViewState["SortColumn"] = e.SortExpression;
        ViewState["SortColumnDirection"] = e.SortDirection; 

        dgvCoaches.DataSource = dt;
        dgvCoaches.DataBind();
    }
}

and here is the aspx code:

<asp:GridView ID="dgvCoaches" runat="server" 
    CssClass="table table-hover table-striped" GridLines="None"  DataKeyNames="HealthCoachID" OnRowCommand="dgvCoaches_RowCommand"
    AutoGenerateColumns="False" OnSorting="gridView_Sorting" AllowSorting="true">
    <Columns>
        <asp:BoundField DataField="HealthCoachID" Visible="false" />
        <asp:BoundField DataField="LastName" HeaderText="Last Name" SortExpression="LastName" />
        <asp:BoundField DataField="FirstName" HeaderText="First Name" SortExpression="FirstName" />
        <asp:BoundField DataField="LoginName" HeaderText="Login Name" SortExpression="LoginName" />
        <asp:BoundField DataField="Email" HeaderText="Email" SortExpression="Email" HtmlEncode="false" DataFormatString="<a href=mailto:{0}>{0}</a>" />
        <asp:TemplateField>
            <ItemTemplate>
                    <asp:LinkButton runat="server" BorderStyle="None" CssClass="btn btn-default" Text="<i class='glyphicon glyphicon-edit'></i>" CommandName="Update" CommandArgument="<%# ((GridViewRow) Container).RowIndex %>" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:LinkButton runat="server" OnClientClick="return ConfirmOnDelete();" BorderStyle="None" CssClass="btn btn-default" Text="<i class='glyphicon glyphicon-remove'></i>" CommandName="Delete" CommandArgument="<%# ((GridViewRow) Container).RowIndex %>" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
    <RowStyle CssClass="cursor-pointer" />
</asp:GridView>

        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" AllowSorting="True" 
            onsorting="GridView1_Sorting" EnableViewState="true">
            <Columns><asp:BoundField DataField="bookid" HeaderText="BOOK ID" SortExpression="bookid"  />
                <asp:BoundField DataField="bookname" HeaderText="BOOK NAME" />
                <asp:BoundField DataField="writer" HeaderText="WRITER" />
                <asp:BoundField DataField="totalbook" HeaderText="TOTAL BOOK" SortExpression="totalbook"  />
                <asp:BoundField DataField="availablebook" HeaderText="AVAILABLE BOOK" />
//gridview code on page load under ispostback false//after that.



protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            string query = "SELECT * FROM book";
            DataTable DT = new DataTable();
            SqlDataAdapter DA = new SqlDataAdapter(query, sqlCon);
            DA.Fill(DT);


            GridView1.DataSource = DT;
            GridView1.DataBind();
        }
    }

    protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
    {

        string query = "SELECT * FROM book";
        DataTable DT = new DataTable();
        SqlDataAdapter DA = new SqlDataAdapter(query, sqlCon);
        DA.Fill(DT);

        GridView1.DataSource = DT;
        GridView1.DataBind();

        if (DT != null)
        {

            DataView dataView = new DataView(DT);
            dataView.Sort = e.SortExpression + " " + ConvertSortDirectionToSql(e.SortDirection);


            GridView1.DataSource = dataView;
            GridView1.DataBind();
        }
    }

    private string GridViewSortDirection
    {
        get { return ViewState["SortDirection"] as string ?? "DESC"; }
        set { ViewState["SortDirection"] = value; }
    }

    private string ConvertSortDirectionToSql(SortDirection sortDirection)
    {
        switch (GridViewSortDirection)
        {
            case "ASC":
                GridViewSortDirection = "DESC";
                break;

            case "DESC":
                GridViewSortDirection = "ASC";
                break;
        }

        return GridViewSortDirection;
    }
}

It's been awhile since I used a GridView, but I think you need to set the grid's SortDirection property to whatever it currently is before leaving the OnSorting method.

So....

List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, e.SortDirection));
grdHeader.SortDirection = e.SortDirection.Equals(SortDirection.Ascending) ? SortDirection.Descending : SortDirection.Ascending;
grdHeader.DataSource = items;
grdHeader.DataBind();


XML:

<asp:BoundField DataField="DealCRMID" HeaderText="Opportunity ID"
 SortExpression="DealCRMID"/>
<asp:BoundField DataField="DealCustomerName" HeaderText="Customer" 
 SortExpression="DealCustomerName"/>
<asp:BoundField DataField="SLCode" HeaderText="Practice" 
 SortExpression="SLCode"/>

Code:

private string ConvertSortDirectionToSql(String sortExpression,SortDirection sortDireciton)
{
    switch (sortExpression)
    {
        case "DealCRMID":
             ViewState["DealCRMID"]=ChangeSortDirection(ViewState["DealCRMID"].ToString());
             return ViewState["DealCRMID"].ToString();

        case "DealCustomerName":
             ViewState["DealCustomerName"] = ChangeSortDirection(ViewState["DealCustomerName"].ToString());
             return ViewState["DealCustomerName"].ToString();

        case "SLCode":
             ViewState["SLCode"] = ChangeSortDirection(ViewState["SLCode"].ToString());
             return ViewState["SLCode"].ToString();

        default:
            return "ASC";
    }       
}

private string ChangeSortDirection(string sortDireciton)
{
    switch (sortDireciton)
    {
        case "DESC":
            return "ASC";
        case "ASC":
            return "DESC";
        default:
            return "ASC";
    }
}

protected void gvPendingApprovals_Sorting(object sender, GridViewSortEventArgs e)
{
    DataSet ds = (System.Data.DataSet)(gvPendingApprovals.DataSource);

    if(ds.Tables.Count>0)
    {
        DataView m_DataView = new DataView(ds.Tables[0]);
        m_DataView.Sort = e.SortExpression + " " + ConvertSortDirectionToSql   (e.SortExpression.ToString(), e.SortDirection);

        gvPendingApprovals.DataSource = m_DataView;
        gvPendingApprovals.DataBind();
    }
}

I got tired of dealing with this issue and put the sort direction and sort column in the ViewState....


        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" AllowSorting="True" 
            onsorting="GridView1_Sorting" EnableViewState="true">
            <Columns><asp:BoundField DataField="bookid" HeaderText="BOOK ID" SortExpression="bookid"  />
                <asp:BoundField DataField="bookname" HeaderText="BOOK NAME" />
                <asp:BoundField DataField="writer" HeaderText="WRITER" />
                <asp:BoundField DataField="totalbook" HeaderText="TOTAL BOOK" SortExpression="totalbook"  />
                <asp:BoundField DataField="availablebook" HeaderText="AVAILABLE BOOK" />
//gridview code on page load under ispostback false//after that.



protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            string query = "SELECT * FROM book";
            DataTable DT = new DataTable();
            SqlDataAdapter DA = new SqlDataAdapter(query, sqlCon);
            DA.Fill(DT);


            GridView1.DataSource = DT;
            GridView1.DataBind();
        }
    }

    protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
    {

        string query = "SELECT * FROM book";
        DataTable DT = new DataTable();
        SqlDataAdapter DA = new SqlDataAdapter(query, sqlCon);
        DA.Fill(DT);

        GridView1.DataSource = DT;
        GridView1.DataBind();

        if (DT != null)
        {

            DataView dataView = new DataView(DT);
            dataView.Sort = e.SortExpression + " " + ConvertSortDirectionToSql(e.SortDirection);


            GridView1.DataSource = dataView;
            GridView1.DataBind();
        }
    }

    private string GridViewSortDirection
    {
        get { return ViewState["SortDirection"] as string ?? "DESC"; }
        set { ViewState["SortDirection"] = value; }
    }

    private string ConvertSortDirectionToSql(SortDirection sortDirection)
    {
        switch (GridViewSortDirection)
        {
            case "ASC":
                GridViewSortDirection = "DESC";
                break;

            case "DESC":
                GridViewSortDirection = "ASC";
                break;
        }

        return GridViewSortDirection;
    }
}

Another one :) Don't need to hard code column names..

DataTable dt = GetData();

    SortDirection sd;
    string f;
    GridViewSortDirection(gvProductBreakdown, e, out sd, out f);
    dt.DefaultView.Sort = sd == SortDirection.Ascending ? f + " asc" : f + " desc";
    gvProductBreakdown.DataSource = dt;
    gvProductBreakdown.DataBind();

Ant then:

 private void GridViewSortDirection(GridView g, GridViewSortEventArgs e, out SortDirection d, out string f)
    {
        f = e.SortExpression;
        d = e.SortDirection;
 if (g.Attributes[f] != null)
        {
            d = g.Attributes[f] == "ASC" ? SortDirection.Descending : SortDirection.Ascending;

            g.Attributes[f] = d == SortDirection.Ascending ? "ASC" : "DESC";
        }
        else
        {
            g.Attributes[f] = "ASC";
            d = SortDirection.Ascending;
        }

The problem with Session and Viewstate is that you also have to keep track of the gridview control for which SortColumn and Direction is stored if there is more than one gridview on the page.

An alternative to Session and Viewstate is to add 2 attributes to the Gridview and keep track of Column and Direction that way.

Here is an example:

private void GridViewSortDirection(GridView g, GridViewSortEventArgs e, out SortDirection d, out string f)
{
    f = e.SortExpression;
    d = e.SortDirection;

    //Check if GridView control has required Attributes
    if (g.Attributes["CurrentSortField"] != null && g.Attributes["CurrentSortDir"] != null)
    {
        if (f == g.Attributes["CurrentSortField"])
        {
            d = SortDirection.Descending;
            if (g.Attributes["CurrentSortDir"] == "ASC")
            {
                d = SortDirection.Ascending;
            }
        }

        g.Attributes["CurrentSortField"] = f;
        g.Attributes["CurrentSortDir"] = (d == SortDirection.Ascending ? "DESC" : "ASC");
    }

}

To toggle ascending and descending, I use a method in my app's BasePage to cache the sort expression and sort direction:

protected void SetPageSort(GridViewSortEventArgs e)
{
    if (e.SortExpression == SortExpression)
    {
        if (SortDirection == "ASC")
        {
            SortDirection = "DESC";
        }
        else
        {
            SortDirection = "ASC";
        }
    }
    else
    {
        SortDirection = "ASC";
        SortExpression = e.SortExpression;
    }
}

SortExpression and SortDirection are both properties in BasePage that store and retrieve their values from ViewState.

So all of my derived pages just call SetPageSort from the GridView's Sorting method, and bind the GridView:

protected void gv_Sorting(object sender, GridViewSortEventArgs e)
{
    SetPageSort(e);
    BindGrid();
}

BindGrid checks the SortExpression and uses it and SortDirection to do an ORDERY BY on the grid's data source, something like this:

if (SortExpression.Length > 0)
{
    qry.ORDER_BY(SortExpression + " " + SortDirection);
}

gv.DataSource = qry.ExecuteReader();
gv.DataBind();

So, the base class' SetPageSort removes much of the drudgery of GridView sorting. I feel like I'm forgetting something, but that's the general idea.


Wrote this, it works for me:

 protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
    {
        if (ViewState["sortExpression"] == null || ViewState["sortExpression"].ToString() != e.SortExpression.ToString())
            MyDataTable.DefaultView.Sort = e.SortExpression + " ASC";
        else
        {
            if (ViewState["SortDirection"].ToString() == "Ascending")
                MyDataTable.DefaultView.Sort = e.SortExpression = e.SortExpression + " DESC";
            else
                MyDataTable.DefaultView.Sort = e.SortExpression + " ASC";
        }

        GridView1.DataSource = MyDataTable;
        GridView1.DataBind();

        ViewState["sortExpression"] = e.SortExpression;
        ViewState["SortDirection"] = e.SortDirection;
    }

I got tired of dealing with this issue and put the sort direction and sort column in the ViewState....


XML:

<asp:BoundField DataField="DealCRMID" HeaderText="Opportunity ID"
 SortExpression="DealCRMID"/>
<asp:BoundField DataField="DealCustomerName" HeaderText="Customer" 
 SortExpression="DealCustomerName"/>
<asp:BoundField DataField="SLCode" HeaderText="Practice" 
 SortExpression="SLCode"/>

Code:

private string ConvertSortDirectionToSql(String sortExpression,SortDirection sortDireciton)
{
    switch (sortExpression)
    {
        case "DealCRMID":
             ViewState["DealCRMID"]=ChangeSortDirection(ViewState["DealCRMID"].ToString());
             return ViewState["DealCRMID"].ToString();

        case "DealCustomerName":
             ViewState["DealCustomerName"] = ChangeSortDirection(ViewState["DealCustomerName"].ToString());
             return ViewState["DealCustomerName"].ToString();

        case "SLCode":
             ViewState["SLCode"] = ChangeSortDirection(ViewState["SLCode"].ToString());
             return ViewState["SLCode"].ToString();

        default:
            return "ASC";
    }       
}

private string ChangeSortDirection(string sortDireciton)
{
    switch (sortDireciton)
    {
        case "DESC":
            return "ASC";
        case "ASC":
            return "DESC";
        default:
            return "ASC";
    }
}

protected void gvPendingApprovals_Sorting(object sender, GridViewSortEventArgs e)
{
    DataSet ds = (System.Data.DataSet)(gvPendingApprovals.DataSource);

    if(ds.Tables.Count>0)
    {
        DataView m_DataView = new DataView(ds.Tables[0]);
        m_DataView.Sort = e.SortExpression + " " + ConvertSortDirectionToSql   (e.SortExpression.ToString(), e.SortDirection);

        gvPendingApprovals.DataSource = m_DataView;
        gvPendingApprovals.DataBind();
    }
}

A simple solution:

protected SortDirection GetSortDirection(string column)
{
    SortDirection nextDir = SortDirection.Ascending; // Default next sort expression behaviour.
    if (ViewState["sort"] != null && ViewState["sort"].ToString() == column)
    {   // Exists... DESC.
        nextDir = SortDirection.Descending;
        ViewState["sort"] = null;
    }
    else
    {   // Doesn't exists, set ViewState.
        ViewState["sort"] = column;
    }
    return nextDir;
}

Much like the default GridView sorting and lightweight on the ViewState.

USAGE:

protected void grdHeader_OnSorting(object sender, GridViewSortEventArgs e)
{
    List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();

    items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, GetSortDirection(e.SortExpression));
    grdHeader.DataSource = items;
    grdHeader.DataBind();
}

Automatic bidirectional sorting only works with the SQL data source. Unfortunately, all the documentation in MSDN assumes you are using that, so GridView can get a bit frustrating.

The way I do it is by keeping track of the order on my own. For example:

    protected void OnSortingResults(object sender, GridViewSortEventArgs e)
    {
        // If we're toggling sort on the same column, we simply toggle the direction. Otherwise, ASC it is.
        // e.SortDirection is useless and unreliable (only works with SQL data source).
        if (_sortBy == e.SortExpression)
            _sortDirection = _sortDirection == SortDirection.Descending ? SortDirection.Ascending : SortDirection.Descending;
        else
            _sortDirection = SortDirection.Ascending;

        _sortBy = e.SortExpression;

        BindResults();
    }

I got tired of dealing with this issue and put the sort direction and sort column in the ViewState....


The problem with Session and Viewstate is that you also have to keep track of the gridview control for which SortColumn and Direction is stored if there is more than one gridview on the page.

An alternative to Session and Viewstate is to add 2 attributes to the Gridview and keep track of Column and Direction that way.

Here is an example:

private void GridViewSortDirection(GridView g, GridViewSortEventArgs e, out SortDirection d, out string f)
{
    f = e.SortExpression;
    d = e.SortDirection;

    //Check if GridView control has required Attributes
    if (g.Attributes["CurrentSortField"] != null && g.Attributes["CurrentSortDir"] != null)
    {
        if (f == g.Attributes["CurrentSortField"])
        {
            d = SortDirection.Descending;
            if (g.Attributes["CurrentSortDir"] == "ASC")
            {
                d = SortDirection.Ascending;
            }
        }

        g.Attributes["CurrentSortField"] = f;
        g.Attributes["CurrentSortDir"] = (d == SortDirection.Ascending ? "DESC" : "ASC");
    }

}

protected void gv_Sorting(object sender, GridViewSortEventArgs e)
{
    DataTable dataTable = (DataTable)Cache["GridData"];

    if (dataTable != null)
    {
        DataView dataView = new DataView(dataTable);
        string Field1 = e.SortExpression;
        string whichWay = "ASC";
        if (HttpContext.Current.Session[Field1] != null)
        {
            whichWay = HttpContext.Current.Session[Field1].ToString();
            if (whichWay == "ASC")
                whichWay = "DESC";
            else
                whichWay = "ASC";               
        }

        HttpContext.Current.Session[Field1] = whichWay;
        dataView.Sort = Field1 + " " + whichWay;      
        gv.DataSource = dataView;
        gv.DataBind();
    }
}

and you store the information that previously was retrieved

    string SqlConn = ConfigurationManager.ConnectionStrings["Sql28"].ConnectionString;
    SqlConnection sqlcon = new SqlConnection(SqlConn);
    sqlcon.Open();

    SqlCommand cmd = new SqlCommand();
    cmd.Connection = sqlcon;
    cmd.CommandType = System.Data.CommandType.Text;
    cmd.CommandText = HttpContext.Current.Session["sql"].ToString();

    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
    DataTable employees = new DataTable();
    adapter.Fill(employees);

    gv.DataSource = employees;
    gv.DataBind();

    Cache.Insert("GridData", employees, null, System.Web.Caching.Cache.NoAbsoluteExpiration,new TimeSpan(0, 360000, 0));

Old string, but maybe my answer will help somebody.

First get your SqlDataSource as a DataView:

Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As DataGridSortCommandEventArgs) Handles grid1.SortCommand
    Dim dataView As DataView = CType(SqlDataSource1.Select(DataSourceSelectArguments.Empty), DataView)
    dataView.Sort = e.SortExpression + dataView.FieldSortDirection(Session, e.SortExpression)

    grid1.DataSourceID = Nothing
    grid1.DataSource = dataView
    grid1.DataBind()

End Sub

Then use an extension method for the sort (kind of a cheep shot, but a good start):

public static class DataViewExtensions
{
    public static string FieldSortDirection(this DataView dataView, HttpSessionState session, string sortExpression)
    {
        const string SORT_DIRECTION = "SortDirection";
        var identifier = SORT_DIRECTION + sortExpression;

        if (session[identifier] != null)
        {
            if ((string) session[identifier] == " ASC")
                session[identifier] = " DESC";
            else if ((string) session[identifier] == " DESC")
                session[identifier] = " ASC";
        }
        else
            session[identifier] = " ASC";

        return (string) session[identifier];
    }
}

To toggle ascending and descending, I use a method in my app's BasePage to cache the sort expression and sort direction:

protected void SetPageSort(GridViewSortEventArgs e)
{
    if (e.SortExpression == SortExpression)
    {
        if (SortDirection == "ASC")
        {
            SortDirection = "DESC";
        }
        else
        {
            SortDirection = "ASC";
        }
    }
    else
    {
        SortDirection = "ASC";
        SortExpression = e.SortExpression;
    }
}

SortExpression and SortDirection are both properties in BasePage that store and retrieve their values from ViewState.

So all of my derived pages just call SetPageSort from the GridView's Sorting method, and bind the GridView:

protected void gv_Sorting(object sender, GridViewSortEventArgs e)
{
    SetPageSort(e);
    BindGrid();
}

BindGrid checks the SortExpression and uses it and SortDirection to do an ORDERY BY on the grid's data source, something like this:

if (SortExpression.Length > 0)
{
    qry.ORDER_BY(SortExpression + " " + SortDirection);
}

gv.DataSource = qry.ExecuteReader();
gv.DataBind();

So, the base class' SetPageSort removes much of the drudgery of GridView sorting. I feel like I'm forgetting something, but that's the general idea.


This is probably going to bet buried here but the solution I came up with which works great for my situation:

Form Load Event looks like this:

private DataTable DataTable1;
protected void Page_Load(object sender, EventArgs e)
{
  DataTable1 = GetDataFromDatabase();
  this.GridView1.DataSource = DataTable1.DefaultView;
  this.GridView1.DataBind();
}

Add two hidden fields on to the page:

<asp:HiddenField runat="server" ID="lastSortDirection" />
<asp:HiddenField runat="server" ID="lastSortExpression" />

Add the following to your asp:GridView object:

AllowSorting="True" OnSorting="GridView1_Sorting"

Use the following GridView Sorting Event

protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
    if (lastSortExpression.Value == e.SortExpression.ToString())
    {
        if (lastSortDirection.Value == SortDirection.Ascending.ToString())
        {
            e.SortDirection = SortDirection.Descending;
        }
        else
        {
            e.SortDirection = SortDirection.Ascending;
        }
        lastSortDirection.Value = e.SortDirection.ToString();
        lastSortExpression.Value = e.SortExpression;
    }
    else
    {
        lastSortExpression.Value = e.SortExpression;
        e.SortDirection = SortDirection.Ascending;
        lastSortDirection.Value = e.SortDirection.ToString();
    }

    DataView dv = DataTable1.DefaultView;
    if (e.SortDirection == SortDirection.Ascending)
    {
        dv.Sort = e.SortExpression;
    }
    else
    {
        dv.Sort = e.SortExpression + " DESC";
    }

    DataTable1 = dv.ToTable();
    GridView1.DataSource = DataTable1.DefaultView;
    GridView1.DataBind();
}

Now every column in my gridview is sorted without needing any further changes if any of the columns change.


Automatic bidirectional sorting only works with the SQL data source. Unfortunately, all the documentation in MSDN assumes you are using that, so GridView can get a bit frustrating.

The way I do it is by keeping track of the order on my own. For example:

    protected void OnSortingResults(object sender, GridViewSortEventArgs e)
    {
        // If we're toggling sort on the same column, we simply toggle the direction. Otherwise, ASC it is.
        // e.SortDirection is useless and unreliable (only works with SQL data source).
        if (_sortBy == e.SortExpression)
            _sortDirection = _sortDirection == SortDirection.Descending ? SortDirection.Ascending : SortDirection.Descending;
        else
            _sortDirection = SortDirection.Ascending;

        _sortBy = e.SortExpression;

        BindResults();
    }

The way I did this is similar to the code that the accepted answer provided, bit is a bit different so I thought I would put it out there as well. Note that this sorting is being done to a DataTable before it is being bound to the GridView.DataSource.

Option One: Using ViewState

void DataGrid_Sorting(object sender, GridViewSortEventArgs e)
{
    if (e.SortExpression == (string)ViewState["SortColumn"])
    {
        // We are resorting the same column, so flip the sort direction
        e.SortDirection = 
            ((SortDirection)ViewState["SortColumnDirection"] == SortDirection.Ascending) ? 
            SortDirection.Descending : SortDirection.Ascending;
    }
    // Apply the sort
    this._data.DefaultView.Sort = e.SortExpression +
        (string)((e.SortDirection == SortDirection.Ascending) ? " ASC" : " DESC");
    ViewState["SortColumn"] = e.SortExpression;
    ViewState["SortColumnDirection"] = e.SortDirection;
}

Option Two: Using Session

Note that the following is being provided for legacy purposes in the event that you see it in the field, or that you are still supporting company systems that are targeting older browsers.

void DataGrid_Sorting(object sender, GridViewSortEventArgs e)
{
    if (e.SortExpression == (string)HttpContext.Current.Session["SortColumn"])
    {
        // We are resorting the same column, so flip the sort direction
        e.SortDirection = 
            ((SortDirection)HttpContext.Current.Session["SortColumnDirection"] == SortDirection.Ascending) ? 
            SortDirection.Descending : SortDirection.Ascending;
    }
    // Apply the sort
    this._data.DefaultView.Sort = e.SortExpression +
        (string)((e.SortDirection == SortDirection.Ascending) ? " ASC" : " DESC");
    HttpContext.Current.Session["SortColumn"] = e.SortExpression;
    HttpContext.Current.Session["SortColumnDirection"] = e.SortDirection;
}

I had a horrible problem with this so I finally resorted to using LINQ to order the DataTable before assigning it to the view:

Dim lquery = From s In listToMap
             Select s
             Order By s.ACCT_Active Descending, s.ACCT_Name

In particular I really found the DataView.Sort and DataGrid.Sort methods unreliable when sorting a boolean field.

I hope this helps someone out there.


Perhaps this will help someone. Not sure if it's because it's 2014 or I don't understand the problem this post trying to resolve but this is very simple with slickgrid as follows:

The issue seems to be how to 'remember' what the current sort setting is so suggestions are around Asp.Net holding that value for you. However slickGrid can tell you what the current sort order is:

To toggle sort asc desc you can use grid.getSortColumns() to find out what the column sort currently is. This is what I did but I am only sorting on 1 column at a time thus I can safely do this : 'if(grid.getSortColumns()[0].sortAsc)'

... so my code which works is like this:

    // Make sure you have sortable: true on the relevant column names or 
    // nothing happens as I found!!
    var columns = [
    { name: "FileName", id: "FileName", field: "FileName", width: 95, selectable: true, sortable: true },
    { name: "Type", id: "DocumentType", field: "DocumentType", minWidth: 105, width: 120, maxWidth: 120, selectable: true, sortable: true },
    { name: "ScanDate", id: "ScanDate", field: "ScanDate", width: 90, selectable: true, sortable: true }, ];

.. load your data as usual then the sort part:

   // Clicking on a column header fires this event. Here we toggle the sort direction
    grid.onHeaderClick.subscribe(function(e, args) {
        var columnID = args.column.id;

        if (grid.getSortColumns()[0].sortAsc) {
            grid.setSortColumn(args.column.id, true);
        }
        else {
            grid.setSortColumn(args.column.id, false);
        }
    });

    // The actual sort function is like this 
        grid.onSort.subscribe(function (e, args) {
            sortdir = args.sortAsc ? 1 : -1;
            sortcol = args.sortCol.field;

            //alert('in sort');

            // using native sort with comparer
            // preferred method but can be very slow in IE with huge datasets
            dataView.sort(comparer, args.sortAsc);
            grid.invalidateAllRows();
            grid.render();
        });

// Default comparer is enough for what I'm doing here ..
function comparer(a, b) {
    var x = a[sortcol], y = b[sortcol];
    return (x == y ? 0 : (x > y ? 1 : -1));
}

Lastly make sure you have the SlickGrid image folder included in your site and you'll get the asc/desc arrows appearing on the column when you select it. If they are missing the text will go italics but no arrows will appear.


Here is how I do. Much easier than alot of the answers here IMO:

Create this SortDirection class

    // ==================================================
    // SortByDirection
    // ==================================================
    public SortDirection SortByDirection
    {
        get
        {
            if (ViewState["SortByDirection"] == null)
            {
                ViewState["SortByDirection"] = SortDirection.Ascending;
            }

            return (SortDirection)Enum.Parse(typeof(SortDirection), ViewState["SortByDirection"].ToString());
        }
        set { ViewState["SortByDirection"] = value; }
    }

And then use it in your sort function like this:

            // Created Date
            if (sortBy == "CreatedDate")
            {
                if (SortByDirection == SortDirection.Ascending)
                {
                    data = data.OrderBy(x => x.CreatedDate).ToList();
                    SortByDirection = SortDirection.Descending;
                }
                else {
                    data = data.OrderByDescending(x => x.CreatedDate).ToList();
                    SortByDirection = SortDirection.Ascending;
                } 
            }

protected void gv_Sorting(object sender, GridViewSortEventArgs e)
{
    DataTable dataTable = (DataTable)Cache["GridData"];

    if (dataTable != null)
    {
        DataView dataView = new DataView(dataTable);
        string Field1 = e.SortExpression;
        string whichWay = "ASC";
        if (HttpContext.Current.Session[Field1] != null)
        {
            whichWay = HttpContext.Current.Session[Field1].ToString();
            if (whichWay == "ASC")
                whichWay = "DESC";
            else
                whichWay = "ASC";               
        }

        HttpContext.Current.Session[Field1] = whichWay;
        dataView.Sort = Field1 + " " + whichWay;      
        gv.DataSource = dataView;
        gv.DataBind();
    }
}

and you store the information that previously was retrieved

    string SqlConn = ConfigurationManager.ConnectionStrings["Sql28"].ConnectionString;
    SqlConnection sqlcon = new SqlConnection(SqlConn);
    sqlcon.Open();

    SqlCommand cmd = new SqlCommand();
    cmd.Connection = sqlcon;
    cmd.CommandType = System.Data.CommandType.Text;
    cmd.CommandText = HttpContext.Current.Session["sql"].ToString();

    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
    DataTable employees = new DataTable();
    adapter.Fill(employees);

    gv.DataSource = employees;
    gv.DataBind();

    Cache.Insert("GridData", employees, null, System.Web.Caching.Cache.NoAbsoluteExpiration,new TimeSpan(0, 360000, 0));

Here is how I do. Much easier than alot of the answers here IMO:

Create this SortDirection class

    // ==================================================
    // SortByDirection
    // ==================================================
    public SortDirection SortByDirection
    {
        get
        {
            if (ViewState["SortByDirection"] == null)
            {
                ViewState["SortByDirection"] = SortDirection.Ascending;
            }

            return (SortDirection)Enum.Parse(typeof(SortDirection), ViewState["SortByDirection"].ToString());
        }
        set { ViewState["SortByDirection"] = value; }
    }

And then use it in your sort function like this:

            // Created Date
            if (sortBy == "CreatedDate")
            {
                if (SortByDirection == SortDirection.Ascending)
                {
                    data = data.OrderBy(x => x.CreatedDate).ToList();
                    SortByDirection = SortDirection.Descending;
                }
                else {
                    data = data.OrderByDescending(x => x.CreatedDate).ToList();
                    SortByDirection = SortDirection.Ascending;
                } 
            }

This is another way of solving the issue:

protected void grdHeader_OnSorting(object sender, GridViewSortEventArgs e)
{
    List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
    items.Sort = e.SortExpression + " " + ConvertSortDirectionToSql(e);
    grdHeader.DataSource = items;
    grdHeader.DataBind();
}

private string ConvertSortDirectionToSql(GridViewSortEventArgs e)
{
    ViewState[e.SortExpression] = ViewState[e.SortExpression] ?? "ASC";
    ViewState[e.SortExpression] = (ViewState[e.SortExpression].ToString() == "ASC") ? "DESC" : "ASC";
    return ViewState[e.SortExpression].ToString();
}

Perhaps this will help someone. Not sure if it's because it's 2014 or I don't understand the problem this post trying to resolve but this is very simple with slickgrid as follows:

The issue seems to be how to 'remember' what the current sort setting is so suggestions are around Asp.Net holding that value for you. However slickGrid can tell you what the current sort order is:

To toggle sort asc desc you can use grid.getSortColumns() to find out what the column sort currently is. This is what I did but I am only sorting on 1 column at a time thus I can safely do this : 'if(grid.getSortColumns()[0].sortAsc)'

... so my code which works is like this:

    // Make sure you have sortable: true on the relevant column names or 
    // nothing happens as I found!!
    var columns = [
    { name: "FileName", id: "FileName", field: "FileName", width: 95, selectable: true, sortable: true },
    { name: "Type", id: "DocumentType", field: "DocumentType", minWidth: 105, width: 120, maxWidth: 120, selectable: true, sortable: true },
    { name: "ScanDate", id: "ScanDate", field: "ScanDate", width: 90, selectable: true, sortable: true }, ];

.. load your data as usual then the sort part:

   // Clicking on a column header fires this event. Here we toggle the sort direction
    grid.onHeaderClick.subscribe(function(e, args) {
        var columnID = args.column.id;

        if (grid.getSortColumns()[0].sortAsc) {
            grid.setSortColumn(args.column.id, true);
        }
        else {
            grid.setSortColumn(args.column.id, false);
        }
    });

    // The actual sort function is like this 
        grid.onSort.subscribe(function (e, args) {
            sortdir = args.sortAsc ? 1 : -1;
            sortcol = args.sortCol.field;

            //alert('in sort');

            // using native sort with comparer
            // preferred method but can be very slow in IE with huge datasets
            dataView.sort(comparer, args.sortAsc);
            grid.invalidateAllRows();
            grid.render();
        });

// Default comparer is enough for what I'm doing here ..
function comparer(a, b) {
    var x = a[sortcol], y = b[sortcol];
    return (x == y ? 0 : (x > y ? 1 : -1));
}

Lastly make sure you have the SlickGrid image folder included in your site and you'll get the asc/desc arrows appearing on the column when you select it. If they are missing the text will go italics but no arrows will appear.


void dg_SortCommand(object source, DataGridSortCommandEventArgs e)
{
    DataGrid dg = (DataGrid) source;
    string sortField = dg.Attributes["sortField"];
    List < SubreportSummary > data = (List < SubreportSummary > ) dg.DataSource;
    string field = e.SortExpression.Split(' ')[0];
    string sort = "ASC";
    if (sortField != null)
    {
        sort = sortField.Split(' ')[0] == field ? (sortField.Split(' ')[1] == "DESC" ? "ASC" : "DESC") : "ASC";
    }
    dg.Attributes["sortField"] = field + " " + sort;
    data.Sort(new GenericComparer < SubreportSummary > (field, sort, null));
    dg.DataSource = data;
    dg.DataBind();
}

Automatic bidirectional sorting only works with the SQL data source. Unfortunately, all the documentation in MSDN assumes you are using that, so GridView can get a bit frustrating.

The way I do it is by keeping track of the order on my own. For example:

    protected void OnSortingResults(object sender, GridViewSortEventArgs e)
    {
        // If we're toggling sort on the same column, we simply toggle the direction. Otherwise, ASC it is.
        // e.SortDirection is useless and unreliable (only works with SQL data source).
        if (_sortBy == e.SortExpression)
            _sortDirection = _sortDirection == SortDirection.Descending ? SortDirection.Ascending : SortDirection.Descending;
        else
            _sortDirection = SortDirection.Ascending;

        _sortBy = e.SortExpression;

        BindResults();
    }

In vb.net but very simple!

Protected Sub grTicketHistory_Sorting(sender As Object, e As GridViewSortEventArgs) Handles grTicketHistory.Sorting

    Dim dt As DataTable = Session("historytable")
    If Session("SortDirection" & e.SortExpression) = "ASC" Then
        Session("SortDirection" & e.SortExpression) = "DESC"
    Else
        Session("SortDirection" & e.SortExpression) = "ASC"
    End If
    dt.DefaultView.Sort = e.SortExpression & " " & Session("SortDirection" & e.SortExpression)
    grTicketHistory.DataSource = dt
    grTicketHistory.DataBind()

End Sub

void dg_SortCommand(object source, DataGridSortCommandEventArgs e)
{
    DataGrid dg = (DataGrid) source;
    string sortField = dg.Attributes["sortField"];
    List < SubreportSummary > data = (List < SubreportSummary > ) dg.DataSource;
    string field = e.SortExpression.Split(' ')[0];
    string sort = "ASC";
    if (sortField != null)
    {
        sort = sortField.Split(' ')[0] == field ? (sortField.Split(' ')[1] == "DESC" ? "ASC" : "DESC") : "ASC";
    }
    dg.Attributes["sortField"] = field + " " + sort;
    data.Sort(new GenericComparer < SubreportSummary > (field, sort, null));
    dg.DataSource = data;
    dg.DataBind();
}

I had a horrible problem with this so I finally resorted to using LINQ to order the DataTable before assigning it to the view:

Dim lquery = From s In listToMap
             Select s
             Order By s.ACCT_Active Descending, s.ACCT_Name

In particular I really found the DataView.Sort and DataGrid.Sort methods unreliable when sorting a boolean field.

I hope this helps someone out there.


This is probably going to bet buried here but the solution I came up with which works great for my situation:

Form Load Event looks like this:

private DataTable DataTable1;
protected void Page_Load(object sender, EventArgs e)
{
  DataTable1 = GetDataFromDatabase();
  this.GridView1.DataSource = DataTable1.DefaultView;
  this.GridView1.DataBind();
}

Add two hidden fields on to the page:

<asp:HiddenField runat="server" ID="lastSortDirection" />
<asp:HiddenField runat="server" ID="lastSortExpression" />

Add the following to your asp:GridView object:

AllowSorting="True" OnSorting="GridView1_Sorting"

Use the following GridView Sorting Event

protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
    if (lastSortExpression.Value == e.SortExpression.ToString())
    {
        if (lastSortDirection.Value == SortDirection.Ascending.ToString())
        {
            e.SortDirection = SortDirection.Descending;
        }
        else
        {
            e.SortDirection = SortDirection.Ascending;
        }
        lastSortDirection.Value = e.SortDirection.ToString();
        lastSortExpression.Value = e.SortExpression;
    }
    else
    {
        lastSortExpression.Value = e.SortExpression;
        e.SortDirection = SortDirection.Ascending;
        lastSortDirection.Value = e.SortDirection.ToString();
    }

    DataView dv = DataTable1.DefaultView;
    if (e.SortDirection == SortDirection.Ascending)
    {
        dv.Sort = e.SortExpression;
    }
    else
    {
        dv.Sort = e.SortExpression + " DESC";
    }

    DataTable1 = dv.ToTable();
    GridView1.DataSource = DataTable1.DefaultView;
    GridView1.DataBind();
}

Now every column in my gridview is sorted without needing any further changes if any of the columns change.


It's been awhile since I used a GridView, but I think you need to set the grid's SortDirection property to whatever it currently is before leaving the OnSorting method.

So....

List<V_ReportPeriodStatusEntity> items = GetPeriodStatusesForScreenSelection();
items.Sort(new Helpers.GenericComparer<V_ReportPeriodStatusEntity>(e.SortExpression, e.SortDirection));
grdHeader.SortDirection = e.SortDirection.Equals(SortDirection.Ascending) ? SortDirection.Descending : SortDirection.Ascending;
grdHeader.DataSource = items;
grdHeader.DataBind();


Examples related to asp.net

RegisterStartupScript from code behind not working when Update Panel is used You must add a reference to assembly 'netstandard, Version=2.0.0.0 No authenticationScheme was specified, and there was no DefaultChallengeScheme found with default authentification and custom authorization How to use log4net in Asp.net core 2.0 Visual Studio 2017 error: Unable to start program, An operation is not legal in the current state How to create roles in ASP.NET Core and assign them to users? How to handle Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause() ASP.NET Core Web API Authentication Could not load file or assembly 'CrystalDecisions.ReportAppServer.CommLayer, Version=13.0.2000.0 WebForms UnobtrusiveValidationMode requires a ScriptResourceMapping for jquery

Examples related to sorting

Sort Array of object by object field in Angular 6 Sorting a list with stream.sorted() in Java How to sort dates from Oldest to Newest in Excel? how to sort pandas dataframe from one column Reverse a comparator in Java 8 Find the unique values in a column and then sort them pandas groupby sort within groups pandas groupby sort descending order Efficiently sorting a numpy array in descending order? Swift: Sort array of objects alphabetically

Examples related to gridview

Simple Android grid example using RecyclerView with GridLayoutManager (like the old GridView) Android Recyclerview GridLayoutManager column spacing Get the cell value of a GridView row add new row in gridview after binding C#, ASP.net Check if list is empty in C# How to refresh Gridview after pressed a button in asp.net Putting GridView data in a DataTable How to get row data by clicking a button in a row in an ASP.NET gridview Change header text of columns in a GridView Twitter Bootstrap and ASP.NET GridView