2016.09.08 It's been entirely too long since I've written one of these. I thought I'd walk you through how I made the datagrid I'm using on the maintenance form. To me, the thing that makes the datagrid so great is the same thing that makes it so scary to build: it's flexibility. You can do a TON with it, but it's going to take some work. I made some upgrades to this maintenance grid, in that I implemented paging and sorting. Where sorting is concerned, it's a more complicated sort that uses up and down arrows in each column to let the user select a sort order -- we're beyond the simple SortExpression argument you may be used to seeing. You might have guessed that those kinds of upgrades mean that we're headed for some TemplateColumn action. So let's jump in. . . . There's a lot going on within the header template here -- it's a label and a span which includes two link buttons -- one up arrow and one down arrow. The span is styled so that the arrows are grouped together on the right side of each column. SORTING The link buttons are the secret sauce. The text of each are HTML symbols -- one up arrow ("↑") and one down arrow ("↓"). The OnCommand() function points to an event handler called Grid_SortCommand. The CommandName and CommandArgument properties are passed directly into the event handler through the event args ("e"). The event handler code follows: Protected Sub Grid_SortCommand(ByVal sender As Object, ByVal e As CommandEventArgs) 'copy the sort command into a variable Dim strSortCommand As String = CType((e.CommandName & " " & e.CommandArgument), String) 'copy the sort command into viewstate ViewState("sort") = strSortCommand 'get the dataset again Call Page_GetData() 'create a dataview from the dataset Dim dvDataView As New DataView(DsDataSet.Tables(0)) With dvDataView 'filter out items that are not tasks .RowFilter = "active = 'True' AND section = 'tasks'" 'sort based on what was clicked .Sort = CType(ViewState("sort"), String) End With 'bind With grdMaint .DataSource = dvDataView .DataBind() End With 'tidy up dvDataView.Dispose() End Sub Notice that I'm sticking the sort into ViewState. I'm doing this primarily to ensure that the sort gets preserved regardless of the current page being displayed. I didn't want to take a chance on the sort order getting reset to default each time the user moves from page to page. This type of event handler takes an argument of type CommandEventArgs. This type allows me to pass in the CommandName and CommandArgument values. How convenient that we can insert our SortCommand value in the CommandName argument, and our direction in the CommandArgument argument! I can put the two together in a string and that becomes our sort value. But wait! There's more... Because we also want to be able to add some nice touches, like the ability to make the arrow you clicked on stand out, we're going to also need an ItemDataBound event handler. Private Sub Grid_ItemDataBound(ByVal sender As Object, ByVal e As DataGridItemEventArgs) Handles grdMaint.ItemDataBound '*** HEADER *** If e.Item.ItemType = ListItemType.Header Then 'pass the sort command into a function that should return the ID of the control that sent it Dim strControlId As String = GetGridSortControl(CType(ViewState("sort"), System.String)) 'create an instance of the source control Dim ctrl As LinkButton = CType(e.Item.FindControl(strControlId), LinkButton) 'set properties. Remember that Enabled is set to true on each control in the code forward by default With ctrl .ForeColor = Color.Orange .ToolTip = "This is the current sort method." .Enabled = False End With End If 'e.Item.ItemType = ListItemType.Header End Sub The very first thing we do in the ItemDataBound event handler is to inspect the row that was just data bound. Notice the data type of the event arguments here is DataGridItemEventArgs, which allows us to do things like find controls by using that objeet. In the code above, I want to ignore all of the list item types that aren't headers, because the control I want to modify is only in the header. Once I know I'm in a header, I need to find the ID of the arrow control that was last clicked to produce the current sort condition. To do that, I have a function that basically walks the current sort condition back to the control that's associated with it. The function isn't as clean as I'd like -- it simply runs through a list, comparing the current sort to a list. I can't help but feel there's a sexier way to do this.... but for right now, I'll go low-tech. Now that I know the control's ID, I create an instance of that LinkButton control, and set the properties -- changing its color (remember, it's text), adding a tooltip, and disabling the link. Recall back in the code forward that values were provided for ForeColor and for Enabled for each of the controls. The postback operation is what gives us the restoration of the default conditions as sorts are changed. PAGING Paging isn't handled so strangely. It does require its own event handler: Private Sub Grid_PageChanged(ByVal sender As Object, ByVal e As DataGridPageChangedEventArgs) Handles grdMaint.PageIndexChanged grdMaint.CurrentPageIndex = e.NewPageIndex 'get the dataset again Call Page_GetData() 'create a dataview from the dataset Dim dvDataView As New DataView(DsDataSet.Tables(0)) With dvDataView 'filter out items that are not tasks .RowFilter = "active = 'True' AND section = 'tasks'" 'sort based on what was last stored in ViewState .Sort = CType(ViewState("sort"), String) End With 'bind With grdMaint .DataSource = dvDataView .DataBind() End With 'tidy up dvDataView.Dispose() End Sub The main attraction for that sub is in the first line -- the CurrentPageIndex property is changed to the value that "e" brought to the party (note the data type is DataGridPageChangedEventArgs). Again, because we're using ViewState for our sort criteria, we're able to set the same criteria as the grid is rebound with its new CurrentPageIndex value. All three columns are basically the same, but there's one other trick I'd like to share with you. In one of the item templates, I have the title attribute set. It's pointing to a description field in my recordset, like this: I want that title to change based on whether or not the task it describes has been completed. So we're back to the Grid_ItemDataBound event handler. I'll just show the goop related to the item template: '*** CONTENT ITEMS *** If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then Try 'create an instance of lblCompletedDate. 'you don't have to identify the cell it's in. Dim lblCompletedDate As Label = CType(e.Item.FindControl("lblCompletedDate"), Label) Dim lblSummary As Label = CType(e.Item.FindControl("lblSummary"), Label) Dim strToolTip As String 'test the text. If it's not blank, then set the font style for the row to strikeout, 'and prefix the title to read "COMPLETED." If lblCompletedDate.Text.Trim <> String.Empty Then e.Item.Font.Strikeout = True strToolTip = lblSummary.Attributes("Title") lblSummary.ToolTip = "COMPLETED: " & strToolTip End If Catch ex As Exception End Try End If 'e.Item.ItemType = ListItemType.Item... The difference between the tooltip and title attributes is really a difference in technologies. Title is an attribute in HTML. Tooltip is an attribute assignable to form controls. In order to prepend the word "COMPLETED" to the description, we need to pull out the string assigned to the Title attribute, then assign the word "COMPLETED" followed by the title text to the Tooltip property of the label control. This operation is predicated on the absencce of a completed date in the appropriate label control -- so you'll create an instance of that label control just as you did the summary label. One final note -- I've assigned an alternating item style to the datagrid, so adjacent rows may be read more easily. This is accomplished by the node at the bottom of the control: . . . That should be it for now! Contact me using the site's contact form if you have questions. Feel free to use the code in your projects. A shout out in your project would be thoughtful. Also, drop me a line and let me know how you might have tweaked things to better suit your needs. Finally, I wouldn't profess to be THE expert on matters represented in my code -- so drop me a line if you have constructive suggestions, too. I'd like to hear from you! Best, halfgk copyright 2016 halfgk.com