In this article, I will show you how to manually sort the GridView WebControl and display sort direction arrows. The GridView has built-in sorting capabilities, however if we want visual feedback as to what column is being sorted and to what direction, we have to perform this ourselves. While extending the GridView WebControl would be optimal, I'm going to show a quick way to get it done without creating a new GridView control. Maybe in a future post I'll show how we can create a custom GridView control with sort arrows. Here is a picture of what our final sorted GridView will look like.
Here is the HTML markup of our GridView:
1: <asp:GridView
2: ID="GridView1"
3: runat="server"
4: AutoGenerateColumns="False"
5: DataKeyNames="CustomerID"
6: CssClass="gridview"
7: RowStyle-CssClass="gridview_itm"
8: AlternatingRowStyle-CssClass="gridview_aitm"
9: HeaderStyle-CssClass="gridview_hdr"
10: PagerStyle-CssClass="gridview_pgr">
11: <Columns>
12: <asp:TemplateField>
13: <HeaderTemplate>
14: <asp:LinkButton ID="CustomerID_SortLnkBtn" runat="server" Text="Customer ID:" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CustomerID" CausesValidation="false" />
15: <asp:ImageButton ID="CustomerID_SortImgBtn" runat="server" Visible="false" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CustomerID" CausesValidation="false" />
16: </HeaderTemplate>
17: <ItemTemplate><%#Eval("CustomerID")%></ItemTemplate>
18: </asp:TemplateField>
19:
20: <asp:TemplateField>
21: <HeaderTemplate>
22: <asp:LinkButton ID="CompanyName_SortLnkBtn" runat="server" Text="Company Name:" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CompanyName" CausesValidation="false" />
23: <asp:ImageButton ID="CompanyName_SortImgBtn" runat="server" Visible="false" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CompanyName" CausesValidation="false" />
24: </HeaderTemplate>
25: <ItemTemplate><%#Eval("CompanyName")%></ItemTemplate>
26: </asp:TemplateField>
27:
28: <asp:TemplateField>
29: <HeaderTemplate>
30: <asp:LinkButton ID="ContactName_SortLnkBtn" runat="server" Text="Contact Name:" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="ContactName" CausesValidation="false" />
31: <asp:ImageButton ID="ContactName_SortImgBtn" runat="server" Visible="false" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="ContactName" CausesValidation="false" />
32: </HeaderTemplate>
33: <ItemTemplate><%#Eval("ContactName")%></ItemTemplate>
34: </asp:TemplateField>
35: </Columns>
36: </asp:GridView>
The first step is to acquire customers from the Northwind database in the form of a DataTable. We will then acquire a DataView object from our DataTable, and sort the view. Once the data in our DataView has been sorted, we will then bind the GridView to the sorted DataView. To accomplish the data retrieval, sorting, and data binding, I've created the following method:
1: Private Sub GridView1_DataBind()
2: Dim dt As DataTable = New DataTable()
3:
4: 'fill our datatable w/ customers from the DB
5: Using conn As New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("NorthwindConnectionString").ConnectionString)
6: Dim sql As String = "SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WITH (NOLOCK)"
7: Dim cmd As SqlCommand = New SqlCommand(sql, conn)
8: Dim reader As SqlDataReader = Nothing
9:
10: Try
11: conn.Open()
12: reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
13: dt.Load(reader)
14: Finally
15: If Not reader Is Nothing AndAlso Not reader.IsClosed Then
16: reader.Close()
17: End If
18: End Try
19: End Using
20:
21: If dt.Rows.Count > 0 Then
22: 'get a dataView object from our dataTable of customers
23: Dim dv As DataView = dt.DefaultView
24:
25: 'if the user has elected to sort the gridview
26: If Not String.IsNullOrEmpty(Me.SortBy("GridView1")) Then
27: 'get the sort expression and apply it to our dataView
28: Dim sortExpr As String = Me.SortBy("GridView1") & " " & IIf(Me.SortDirection("GridView1") = WebControls.SortDirection.Ascending, "ASC", "DESC").ToString()
29: dv.Sort = sortExpr
30: End If
31:
32: 'bind the dataView to our GridView
33: Me.GridView1.DataSource = dv
34: Me.GridView1.DataBind()
35: End If
36: End Sub
The logic is pretty straight forward. Take note to line 28. I am using 2 properties to store what column I am sorting by and what direction it is being sorted. I persist the values in the ViewState and I also pass what GridView I either want to retrieve or store values for. This allows me to have more that 1 sorting GridView on my page at a time using the same 2 properties. Here's the code for the 2 properties to assist us with sorting.
1: ''' <summary>
2: ''' Gets or sets the column name to be sorted.
3: ''' </summary>
4: ''' <param name="GridViewID">The unique ID of the GridView.</param>
5: ''' <value></value>
6: ''' <returns></returns>
7: ''' <remarks></remarks>
8: Private Property SortBy(ByVal GridViewID As String) As String
9: Get
10: Dim o As Object = ViewState(GridViewID & "_SortBy")
11: If Not o Is Nothing Then
12: Return o.ToString()
13: Else
14: Return String.Empty
15: End If
16: End Get
17: Set(ByVal value As String)
18: ViewState(GridViewID & "_SortBy") = value
19: End Set
20: End Property
21:
22: ''' <summary>
23: ''' Gets or sets the sort direction.
24: ''' </summary>
25: ''' <param name="GridViewID">The unique ID of the GridView.</param>
26: ''' <value></value>
27: ''' <returns></returns>
28: ''' <remarks></remarks>
29: Private Property SortDirection(ByVal GridViewID As String) As SortDirection
30: Get
31: Dim o As Object = ViewState(GridViewID & "_SortDirection")
32: If Not o Is Nothing Then
33: Return DirectCast(o, SortDirection)
34: Else
35: Return WebControls.SortDirection.Ascending
36: End If
37: End Get
38: Set(ByVal value As SortDirection)
39: ViewState(GridViewID & "_SortDirection") = value
40: End Set
41: End Property
We have the sort by and sort direction properties (storing/persistance mechanisms). We have the data retrieval, sorting of the data, and data binding method in place. Now what we have to do is think about what events we need to account for. First, on initial page load we'll want to fetch customer data and bind it to our GridView. We'll only want to bind our GridView the first time the page loads and not subsequent postbacks.
1: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
2: If Not Page.IsPostBack Then
3: 'bind the gridview on page first load
4: GridView1_DataBind()
5: End If
6: End Sub
Now we are ready to make the magic happen. We want to handle the GridView's RowDataBound event and either show or hide our up/down arrows if the user has elected to sort a column.
1: Private Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
2: 'if the row being dataBound is the header row - toggle sort image visibility/directions
3: If e.Row.RowType = DataControlRowType.Header Then
4: ToggleSortArrows(e.Row, "GridView1")
5: End If
6: End Sub
7:
8: Private Sub ToggleSortArrows(ByVal headerRow As GridViewRow, ByVal gridViewID As String)
9: Dim sortImgBtn As ImageButton = Nothing
10:
11: 'loop through each cell in the header row
12: For Each tc As TableCell In headerRow.Cells
13: 'loop through each control in the cell
14: For Each c As Control In tc.Controls
15: 'if the control is an image button and is our sort image button
16: If TypeOf c Is ImageButton AndAlso c.ID.EndsWith("SortImgBtn") Then
17: sortImgBtn = DirectCast(c, ImageButton)
18:
19: 'if the image button is in the column being sorted
20: If Me.SortBy(gridViewID) = sortImgBtn.ID.Split(CChar("_"))(0) Then
21: 'show the image button and set its image url (sorted column)
22: sortImgBtn.Visible = True
23: If Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending Then
24: sortImgBtn.ImageUrl = "~/img/uparrow.gif"
25: Else
26: sortImgBtn.ImageUrl = "~/img/dnarrow.gif"
27: End If
28: Else
29: 'hide the image button (not a sorted column)
30: sortImgBtn.Visible = False
31: End If
32: End If
33: Next
34: Next
35: End Sub
Basically what we are doing is detecting if the row being DataBound is the header row or not. If it is, we want to loop through each cell in the header row and get a handle on the column's associated sort image. We use the ID of the sort image to acquire what column it represents and compare it to our SortBy property. If it matches, then we want to show the appropriate sort direction image. We hide the other sort images in non-sorted columns.
The only task we have left to account for is how to fire our sorting event. Take a quick look at our GridView HTML markup. The header row has both a LinkButton and ImageButton that raise a GridView command event to which we pass the column name that the user wants to sort by. We then handle the event in our code behind.
1: Private Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles GridView1.RowCommand
2: If e.CommandName.ToUpper = "SORT" Then
3: InitializeSort(e.CommandArgument, "GridView1")
4: GridView1_DataBind()
5: End If
6: End Sub
7:
8: Private Sub InitializeSort(ByVal sortBy As String, ByVal gridViewID As String)
9: If Me.SortBy(gridViewID) = sortBy Then
10: If Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending Then
11: Me.SortDirection(gridViewID) = WebControls.SortDirection.Descending
12: Else
13: Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending
14: End If
15: Else
16: Me.SortBy(gridViewID) = sortBy
17: Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending
18: End If
19: End Sub
Once we capture our sort row command, we initialize our SortBy and SortDirection properties. We either toggle the direction of the sorted column or the sorted column is a new column to be sorted to which we default the column to be sorted Ascending.
I hope this article helped a bit. It's a quick way to get a GridView sorted with visual indicators (sort arrows) without creating a new custom GridView control.
GridViewSorting_Soln.zip (90.61 kb)