views:

13063

answers:

11

Hi !

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 ?

A: 

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

michaeldelorenzo
Nice suggestion but SortDirection is read only so it doesn't work.So what I thought is right : ASP.NET should set this value...
Julien N
A: 

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

Dining Philanderer
A: 

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.

djuth
+4  A: 

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();
 }
Sander
+9  A: 

You can use a session variable to store the latest Sort Expression and when you sort the grid next time compare the sort expression of the grid with the Session variable which stores last sort expression. If the columns are equal then check the direction of the previous sort and sort in the opposite direction.

Example:

DataTable sourceTable = GridAttendence.DataSource as DataTable;
DataView view = new DataView(sourceTable);
string[] sortData = Session["sortExpression"].ToString().Trim().Split(' ');
if (e.SortExpression == sortData[0])
{
    if (sortData[1] == "ASC")
    {
        view.Sort = e.SortExpression + " " + "DESC";
        this.ViewState["sortExpression"] = e.SortExpression + " " + "DESC";
    }
    else
    {
        view.Sort = e.SortExpression + " " + "ASC";
        this.ViewState["sortExpression"] = e.SortExpression + " " + "ASC";
    }
}
else
{
    view.Sort = e.SortExpression + " " + "ASC";
    this.ViewState["sortExpression"] = e.SortExpression + " " + "ASC";
}
This is what I've done so accepted answer.But you should review your answer : the code is not correctly formated.
Julien N
you can use a switch statement instead, too.
BBetances
Why are you storing the sortExpression in Session instead of ViewState?
Dylan Berry
Indeed - the code above is *wrong* and will break if two grids are viewed at the same time in different browser tabs. Replace Session with ViewState.
tomfanning
+3  A: 

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.

George
In my oppinion this is by far the best answer. Bind you data using an ObjectDataSource and setting dataSourceId instead of doing it old school in the code behind.If you really need to store the sort direction put it in the ViewState and not the Session.
Jan Aagaard
A: 

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;
}
Rob
Wouldn't using the "Page.Cache" object force all users to have the same sort order?
Espo
@Espo - Good catch, I've fixed the code to make use of the Session instead.
Rob
No! Use the ViewState! Session breaks cross-browser tab functionality.
tomfanning
@tomfanning - About time someone told me why that was getting down votes. :)The code above is on the old side (note the date) and at the time we were only targeting IE6 for applications so I've added another option with the code using ViewState instead.As an aside, we have actually moved to ASP.NET MVC where this isn't an issue as we using jQuery to handle the sorting client side instead.
Rob
@rob I still recommend you remove the Session[] implementation, since it will lead to unpredictable behaviour with multiple browser windows/tabs.
tomfanning
@tomfanning - True, but in the event that someone runs in to the format in legacy code I don't want to just remove it since it does serve a historical purpose.
Rob
+1  A: 

I've come to the following solution. It is light enough and simple to implement:

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

This function act exactly like the default GridView sorting behaviour. It is light enough on the ViewState with a single string to serialize. Note that you could use Session instead.

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

That's how I implemented it after some investigation.

May this help somebody.

Maxime
+3  A: 

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

}
rwo
I like your attribute alternative to viewstate.
ProfK
A: 

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

                'Use linq to order the data.
            Dim lquery As IOrderedEnumerable(Of Accounts) = 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.

Daver
+1  A: 

However this post is old but since I have a solution too, so I am posting it here for others

http://getsortdirectionfunction.blogspot.com/

Pawan