• How to add sort arrows to GridView Columns

    Posted on September 26, 2012 by in ASP.NET, C#, Dotnet

    I am always under the impression that adding sorting in GridView is pretty straightforward. If you want to use default sorting, set AllowSorting property on the GridView to true and add sort expressions to all the columns.  You could optimize the whole process by handling the way data is fetched and bound to the GridView.

    If you want your page to be more interactive to the user, you might try adding up & down arrows or images to the GridView.  You would have to tap into the RowCreated event of the GridView and add those images to the header columns. You will be surprised to notice that the SortDirection is always defaulted to ASCENDING.  Here we have to write some custom code to save & retrieve this information to/from viewstate.

    Database Connection:
    I am using AdventureWorks for this article.  Add following connectionstring element to web.config.

    <connectionStrings>
    <add name="Sql" connectionString="Data Source=(local);Initial Catalog=AdventureWorks;User=testuser;Password=testuser;" providerName="System.Data.SqlClient"/>
    </connectionStrings>
    

    Page Design
    Create a new ASP.NET web page and add a GridView to the page.  Sorting is enabled on the GridView by setting AllowSorting to true.  We would like to add up and down arrows on the sorted column. This custom logic will be added to the OnRowCreated event handler gvProducts_RowCreated. We would want our custom sorting logic to be executed when user clicks on header column. So Onsorting property is set to gvProducts_OnSort event handler.  As we are not interested in performing any updates, ProductID, Product Number, Product Name & Price are rendered in Bound Columns.  All the header columns have their corresponding SortExpression property set. Typically this would the datasource (dataset) column name.

    <asp:GridView ID="gvProducts" AutoGenerateColumns="False" runat="server" 
        Width="60%" CssClass="GridViewStyle"
        AllowSorting="true"  OnSorting="gvProducts_OnSort" 
        OnRowCreated="gvProducts_RowCreated">         
    <RowStyle CssClass="RowStyle" />
    <AlternatingRowStyle CssClass="AltRowStyle" />
    <HeaderStyle CssClass="HeaderRowStyle" />
    <Columns>        
        <asp:BoundField DataField="ProductID" HeaderText="Product ID" SortExpression="ProductID"/>  
        <asp:BoundField DataField="ProductNumber" HeaderText="Product Number" SortExpression="ProductNumber"/>  
        <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name"/>  
        <asp:BoundField DataField="ListPrice" HeaderText="Price" DataFormatString="{0:c}" SortExpression="ListPrice"/>                     
    </Columns>  
    </asp:GridView>
    

    Source Code:
    I have created couple of variables to store sortexpression and sortdirection

    //variables to store sortexpression and direction
    private string sortExpression; 
    private SortDirection sortDirection;
    

    In the Page_Load event of the page, we fetch products from the database and bind them to the grid by calling FillGrid method. If the page postback happened because of a postback, we fetch sortdirection and sortexpression from the viewstate and store them in above local variables.

    protected void Page_Load(object sender, EventArgs e)
    {
        //if it is initial load, load the grid
        if (!IsPostBack)
        {
            FillGrid();
        }
        else
        {
            //if it is a postback, fetch SortExpression and Direction from viewstate 
            //and store it in local variables
            if (ViewState["SortExpression"] != null)
                sortExpression = ViewState["SortExpression"].ToString();
            else
                sortExpression = String.Empty;
    
            if (ViewState["SortDirection"] != null)
            {
                if (Convert.ToInt32(ViewState["SortDirection"]) == (int)SortDirection.Ascending)
                {
                    sortDirection = SortDirection.Ascending;
                }
                else
                {
                    sortDirection = SortDirection.Descending;
                }
            }
        }
    }
    

    In the FillGrid method, we check if have products resultset stored in the viewstate. If it exists, we simply use that datset. If not we get that list from database using ADO.NET API and stored into dsProducts variable. Once we have the dataset, we have to apply the sorting on it. This sort expression is calculated based on our local variables sortExpression and sortDirection (eg ProductName DESC). A sorted dataview is created from the dataset and bound to the gridview. When page is initially loaded first time, this sort string will be empty. But when the postback happens because of user clicking on the header column, these 2 variables will have some values and sorting will be applied.

    private void FillGrid()
    {
        DataSet dsProducts = new DataSet();            
        if (ViewState["ProductsDS"] != null)
        {
            dsProducts = (DataSet)ViewState["ProductsDS"];
        }
        else
        {
            //fetch products from database and store it in viewstate for subsequent postbacks
            dsProducts = RetrieveProductsFromDB();
            ViewState["ProductsDS"] = dsProducts;
        }
        //prepare the sort string based on the sortexpression and direction (such as ProductID DESC)
        String sort = String.Empty;
        if (null != sortExpression && String.Empty != sortExpression)
        {
            sort = String.Format("{0} {1}", sortExpression, (sortDirection == SortDirection.Descending) ? "DESC" : "ASC");
        }
        //Sort rows in dsProducts based on sort string and bind the view to the grid
        DataView dv = new DataView(dsProducts.Tables[0], String.Empty, sort, DataViewRowState.CurrentRows);
        gvProducts.DataSource = dv;
        gvProducts.DataBind();
    }
    
    public static DataSet RetrieveProductsFromDB()
    {
        //fetch the connection string from web.config
        string connString = ConfigurationManager.ConnectionStrings["Sql"].ConnectionString;
    
        //SQL statement to fetch entries from products            
        string sql = @"SELECT TOP 10 P.ProductID, P.Name, P.ProductNumber, P.ListPrice FROM Production.Product P";
    
        DataSet dsProducts = new DataSet();
        //Open SQL Connection
        using (SqlConnection conn = new SqlConnection(connString))
        {
            conn.Open();
            //Initialize command object
            using (SqlCommand cmd = new SqlCommand(sql, conn))
            {
                SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                //Fill the result set
                adapter.Fill(dsProducts);
            }
        }
        return dsProducts;
    }
    

    Whenever FillGrid is called, rows in gridview are re-created and OnRowCreated event is raised. Since we have allowsorting to true and have sortexpression specified for header columns, DOTNET framework will create a clickable linkbutton for all those columns and the sortexpression will be set the commandbutton argument of the linkbutton. In our gvProducts_RowCreated event handler, we iterate through all the header columns, will get reference to the linkbutton control and then to the command argument of the button. We compare this command argument to the sortexpression stored in the viewstate to figure out if user clicked on that particular header column. Then based on the sortdirection we either add up arrow image or down arrow image.

    protected void gvProducts_RowCreated(object sender, GridViewRowEventArgs e)
    {
        //check if it is a header row
        //since allowsorting is set to true, column names are added as command arguments to 
        //the linkbuttons by DOTNET API
        if (e.Row.RowType == DataControlRowType.Header)
        {
            LinkButton btnSort;
            Image image;
            //iterate through all the header cells
            foreach (TableCell cell in e.Row.Cells)
            {
                //check if the header cell has any child controls
                if (cell.HasControls())
                {
                    //get reference to the button column
                    btnSort = (LinkButton)cell.Controls[0];
                    image = new Image();
                    if (ViewState["SortExpression"] != null)
                    {                            
                        //see if the button user clicked on and the sortexpression in the viewstate are same
                        //this check is needed to figure out whether to add the image to this header column nor not
                        if (btnSort.CommandArgument == ViewState["SortExpression"].ToString())
                        {                 
                            //following snippet figure out whether to add the up or down arrow
                            //based on the sortdirection
                            if (Convert.ToInt32(ViewState["SortDirection"]) == (int)SortDirection.Ascending)
                            {                                   
                                image.ImageUrl = "/themes/images/uparrow.jpg";
                            }
                            else
                            {                                    
                                image.ImageUrl = "/themes/images/downarrow.jpg";
                            }
                            cell.Controls.Add(image);
                        }                            
                    }
                }
            }
        }
    }
    

    The Sorting event is raised when the hyperlink to sort a column is clicked, but before the GridView control handles the sort operation.  As you have noticed in the above rowcreated event, we rely on sortexpression and direction to make some decisions. We will get and store this information in gvProducts_OnSort event handler. A GridViewSortEventArgs object is passed to the event-handling method, which would contain SortExpression and SortDirection.  The same SortExpression information is stored in viewstate. However we can’t rely on SortDirection passed in event argument, as it is always defaulted to ascending.  We figure out the sortdirection ourselves based on SortExpression and previously stored sortdirection value from viewstate.  Once we have both the values, we call FillGrid so that grid will redraw.

    protected void gvProducts_OnSort(object sender, GridViewSortEventArgs e)
    {
        // SortDirection is always set to "Ascending". So we have to store sortdirection in viewstate
        // which will then be retrieved and used while adding up or down arrows in rowcreated event            
        if (String.Empty != sortExpression)
        {
            if (String.Compare(e.SortExpression, sortExpression, true) == 0)
            {
                sortDirection =
                    (sortDirection == SortDirection.Ascending) ? SortDirection.Descending : SortDirection.Ascending;
            }
            else
            {
                sortDirection = SortDirection.Ascending;                    
            }
        }
        sortExpression = e.SortExpression;                        
        ViewState["SortExpression"] = e.SortExpression;
        ViewState["SortDirection"] = (int)sortDirection;
    
        //rebind the grid
        FillGrid();                           
    }
    

    Run the application and top 10 products ordered by product (with list price greather than zero) are loaded as shown below:
    add sort arrows to gridview 1

    Click on the “Price” column and rows will be sorted by price in ascending order. “Up” arrow image is added next to the “Price” column header.
    add sort arrows to gridview 2

    Click on the “Price” colum header again and rows will be order by Price in descending order. A down arrow is added next to the “Price” column header.
    add sort arrows to gridview 3

    Be Sociable, Share!
      Post Tagged with , , ,

    Written by

    Software architect with over 10 years of proven experience in designing & developing n-tier and web based software applications, for Finance, Telecommunication, Manufacturing, Internet and other Commercial industries. He believes that success depends on one's ability to integrate multiple technologies to solve a simple as well as complicated problem.

    View all articles by

    Email : [email protected]