Extending the GridView Control Like any other control in the ASP.NET framework, if you don't like any aspect of the GridView control, you always have the option of extending the control. In this section, you learn how to extend the GridView control with custom fields. To create a custom field, you can inherit a new class from any of the existing fields or any of the following base classes: DataControlField The base class for all fields. ButtonFieldBase The base class for all button fields, such as the ButtonField and CommandField. In this section, you learn how to create a long text field, a delete button field, and a validated field. Creating a Long Text Field None of the existing GridView fields do a good job of handling large amounts of text. You can fix this problem by creating a custom field, named the LongTextField, which you can use to display the value of text columns regardless of the length of the text. In normal display mode, the LongTextField displays the text in a scrolling <div> tag. In edit display mode, the text appears in a multi-line TextBox control (see Figure 11.23). Figure 11.23. Displaying a long text field. To create a custom field, we'll inherit a new class from the base BoundField control. The custom LongTextField is contained in Listing 11.31. Listing 11.31. LongTextField.vb [View full width] Imports System Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.HtmlControls Namespace myControls ''' <summary> ''' Enables you to display a long text field ''' </summary> Public Class LongTextField Inherits BoundField Private _width As Unit = New Unit("250px") Private _height As Unit = New Unit("60px") ''' <summary> ''' The width of the field ''' </summary> Public Property Width() As Unit Get Return _width End Get Set(ByVal Value As Unit) _width = Value End Set End Property ''' <summary> ''' The height of the field ''' </summary> Public Property Height() As Unit Get Return _height End Get Set(ByVal Value As Unit) _height = Value End Set End Property ''' <summary> ''' Builds the contents of the field ''' </summary> Protected Overrides Sub InitializeDataCell(ByVal cell As DataControlFieldCell, ByVal rowState As DataControlRowState) ' If not editing, show in scrolling div If (rowState And DataControlRowState.Edit) = 0 Then Dim div As HtmlGenericControl = New HtmlGenericControl("div") div.Attributes("class") = "longTextField" div.Style(HtmlTextWriterStyle.Width) = _width.ToString() div.Style(HtmlTextWriterStyle.Height) = _height.ToString() div.Style(HtmlTextWriterStyle.Overflow) = "auto" AddHandler div.DataBinding, AddressOf div_DataBinding cell.Controls.Add(div) Else Dim txtEdit As TextBox = New TextBox() txtEdit.TextMode = TextBoxMode.MultiLine txtEdit.Width = _width txtEdit.Height = _height AddHandler txtEdit.DataBinding, AddressOf txtEdit_DataBinding cell.Controls.Add(txtEdit) End If End Sub ''' <summary> ''' Called when databound in display mode ''' </summary> Private Sub div_DataBinding(ByVal s As Object, ByVal e As EventArgs) Dim div As HtmlGenericControl = CType(s, HtmlGenericControl) ' Get the field value Dim value As Object = Me.GetValue(div.NamingContainer) ' Assign the formatted value div.InnerText = Me.FormatDataValue(value, Me.HtmlEncode) End Sub ''' <summary> ''' Called when databound in edit mode ''' </summary> Private Sub txtEdit_DataBinding(ByVal s As Object, ByVal e As EventArgs) Dim txtEdit As TextBox = CType(s, TextBox) ' Get the field value Dim value As Object = Me.GetValue(txtEdit.NamingContainer) ' Assign the formatted value txtEdit.Text = Me.FormatDataValue(value, Me.HtmlEncode) End Sub End Class End Namespace | In Listing 11.31, the InitializeDataCell() method is overridden. This method is responsible for creating all the controls that the custom field contains. First, a check is made to determine whether the field is being rendered when the row is selected for editing. Notice that a bitwise comparison must be performed with the rowState parameter because the rowState parameter can contain combinations of the values Alternate, Normal, Selected, and Edit (for example, the RowState can be both Alternate and Edit). When the row is not in edit mode, a <div> tag is created to contain the text. An HtmlGenericControl represents the <div> tag. When the GridView is bound to its data source, the <div> tags get the value of its innerText property from the div_DataBinding() method. When the row is selected for editing, a multi-line TextBox control is created. When the GridView is bound to its data source, the TextBox control's Text property gets its value from the txtEdit_DataBinding() method. You can experiment with the LongTextField with the page in Listing 11.32. This page uses the LongTextField to display the value of the Movie Description column. Listing 11.32. ShowLongTextField.aspx <%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <style type="text/css"> .grid td, .grid th { padding:5px; } </style> <title>Show LongTextField</title> </head> <body> <form runat="server"> <div> <asp:GridView Css DataSource DataKeyNames="Id" AutoGenerateColumns="false" AutoGenerateEditButton="true" Runat="server"> <Columns> <asp:BoundField DataField="Title" HeaderText="Movie Title" /> <asp:BoundField DataField="Director" HeaderText="Movie Director" /> <custom:LongTextField DataField="Description" Width="300px" Height="60px" HeaderText="Movie Description" /> </Columns> </asp:GridView> <asp:SqlDataSource ConnectionString="<%$ ConnectionStrings:Movies %>" SelectCommand="SELECT Id, Title, Director, Description FROM Movies" UpdateCommand="UPDATE Movies SET Title=@Title,Director=@Director,Description=@Description WHERE Id=@Id" Runat="server" /> </div> </form> </body> </html> | Creating a Delete Button Field I don't like the Delete button rendered by the GridView control's CommandField. The problem is that it does not provide you with any warning before you delete a record. In this section, we fix this problem by creating a Delete button that displays a client-side confirmation dialog box (see Figure 11.24). Figure 11.24. Displaying a confirmation dialog box. The DeleteButtonField inherits from the ButtonField class. The code for the custom field is contained in Listing 11.33. Listing 11.33. DeleteButtonField.vb Most of the work in Listing 11.33 is handled by the base ButtonField class. The InitializeCell() method is overridden so that the button can be grabbed. The button is added to the cell by the base ButtonField's InitializeCell() method. To create the confirmation dialog box, an onclick attribute is added to the button. If the JavaScript confirm statement returns false, then the button click is canceled. You can test the DeleteButtonField with the page in Listing 11.34. This page enables you to delete records from the Movies database table. Listing 11.34. ShowDeleteButtonField.vb <%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <style type="text/css"> .grid td, .grid th { padding:5px; } </style> <title>Show DeleteButtonField</title> </head> <body> <form runat="server"> <div> <asp:GridView Css DataSource DataKeyNames="Id" AutoGenerateColumns="false" Runat="server"> <Columns> <custom:DeleteButtonField ConfirmText="Are you sure that you want to delete this record?" /> <asp:BoundField DataField="Title" HeaderText="Movie Title" /> <asp:BoundField DataField="Director" HeaderText="Movie Director" /> </Columns> </asp:GridView> <asp:SqlDataSource ConnectionString="<%$ ConnectionStrings:Movies %>" SelectCommand="SELECT Id, Title, Director FROM Movies" DeleteCommand="DELETE Movies WHERE Id=@Id" Runat="server" /> </div> </form> </body> </html> | Creating a Validated Field In this final section, we create a ValidatedField custom field. This field automatically validates the data that a user enters into a GridView when editing a record. The ValidatedField uses a RequiredFieldValidator to check whether a user has entered a value, and a CompareValidator to check whether the value is the correct data type (see Figure 11.25). Figure 11.25. Using the ValidatedField to edit a record. The ValidatedField is a composite field. The field contains three child controlsa TextBox, RequiredFieldValidator, CompareValidatorwrapped up in a container control. The code for the ValidatedField is contained in Listing 11.35. Listing 11.35. ValidatedField.vb [View full width] Imports System Imports System.Collections.Specialized Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls ''' <summary> ''' Adds RequiredFieldValidator and CompareValidator ''' to BoundField ''' </summary> Public Class ValidatedField Inherits BoundField Private _validationDataType As ValidationDataType = ValidationDataType.String Public Property ValidationDataType() As ValidationDataType Get Return _validationDataType End Get Set(ByVal Value As ValidationDataType) _validationDataType = Value End Set End Property ''' <summary> ''' Get value from TextBox ''' </summary> Public Overrides Sub ExtractValuesFromCell(ByVal dictionary As IOrderedDictionary, ByVal cell As DataControlFieldCell, ByVal rowState As DataControlRowState, ByVal includeReadOnly As Boolean) Dim edit As EditContainer = CType(cell.Controls(0), EditContainer) If dictionary.Contains(DataField) Then dictionary(DataField) = edit.Text Else dictionary.Add(DataField, edit.Text) End If End Sub ''' <summary> ''' Called when field is bound to data source ''' </summary> Protected Overrides Sub OnDataBindField(ByVal sender As Object, ByVal e As EventArgs) Dim source As Control = CType(sender, Control) ' Get the field value Dim value As Object = Me.GetValue(source.NamingContainer) ' If the control is a table cell, display the text If TypeOf source Is DataControlFieldCell Then Dim formattedValue As String = Me.FormatDataValue(value, Me.HtmlEncode) If formattedValue = String.Empty Then formattedValue = " " End If CType(source, DataControlFieldCell).Text = formattedValue End If ' If the control is an editor, display the editor If TypeOf source Is EditContainer Then Dim formattedValue As String = String.Empty Select Case _validationDataType Case ValidationDataType.Date Dim vdate As DateTime = CType(value, DateTime) formattedValue = vdate.ToShortDateString() Case ValidationDataType.Currency Dim dec As Decimal = CType(value, Decimal) formattedValue = dec.ToString("F") Case Else formattedValue = value.ToString() End Select CType(source, EditContainer).Text = formattedValue End If End Sub ''' <summary> ''' Build the field ''' </summary> Protected Overrides Sub InitializeDataCell(ByVal cell As DataControlFieldCell, ByVal rowState As DataControlRowState) If (rowState And DataControlRowState.Edit) = 0 Then AddHandler cell.DataBinding, AddressOf Me.OnDataBindField Else Dim editor As EditContainer = New EditContainer(DataField, _validationDataType) AddHandler editor.DataBinding, AddressOf Me.OnDataBindField cell.Controls.Add(editor) End If End Sub End Class ''' <summary> ''' This control is added to the field ''' </summary> Public Class EditContainer Inherits Control Implements INamingContainer Private _dataField As String Private _validationDataType As ValidationDataType Private _txtEdit As TextBox Private _valReq As RequiredFieldValidator Private _valCompare As CompareValidator Public Sub New(ByVal dataField As String, ByVal validationDataType As ValidationDataType) _dataField = dataField _validationDataType = validationDataType End Sub ''' <summary> ''' Expose the TextBox control's Text property ''' </summary> Public Property Text() As String Get EnsureChildControls() Return _txtEdit.Text End Get Set(ByVal Value As String) EnsureChildControls() _txtEdit.Text = Value End Set End Property ''' <summary> ''' Add TextBox, RequiredFieldValidator, and ''' CompareValidator ''' </summary> Protected Overrides Sub CreateChildControls() ' Add the textbox _txtEdit = New TextBox() _txtEdit.ID = "txtEdit" Controls.Add(_txtEdit) ' Add a RequiredFieldValidator control _valReq = New RequiredFieldValidator() _valReq.Display = ValidatorDisplay.Dynamic _valReq.Text = "(required)" _valReq.ControlToValidate = _txtEdit.ID Controls.Add(_valReq) ' Add a CompareValidator control _valCompare = New CompareValidator() _valCompare.Display = ValidatorDisplay.Dynamic _valCompare.Operator = ValidationCompareOperator.DataTypeCheck _valCompare.Type = _validationDataType _valCompare.Text = "(invalid)" _valCompare.ControlToValidate = _txtEdit.ID Controls.Add(_valCompare) End Sub End Class End Namespace | The file in Listing 11.35 contains two classes. It contains the ValidatedField class and the EditContainer class. The ValidatedField class derives from the BoundField class and overrides the InitializeDataCell() method. When a row is not selected for editing, the field simply displays the value of the data item associated with it. When a row is selected for editing, the field creates a new EditContainer control. The EditContainer control contains a TextBox, RequiredFieldValidator, and CompareValidator. Notice that the EditContainer implements the INamingContainer interface. Implementing this interface prevents naming collisions when more than one instance of the ValidatedField is used in a GridView row. The ValidatedField is used in the page in Listing 11.36. This page contains a GridView control that you can use to edit the Movies database table. The GridView control includes three ValidatedFields: one for the Title, DateReleased, and BoxOfficeTotals columns. If you edit a column, and attempt to submit the column without entering a value, then a validation error is displayed. Furthermore, if you attempt to enter a value that is not a date for the DateReleased column or a value that is not a currency amount for the BoxOfficeTotals column, then a validation error is displayed. Listing 11.36. ShowValidatedField.aspx <%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Show ValidatedField</title> </head> <body> <form runat="server"> <div> <asp:GridView DataKeyNames="Id" DataSource AutoGenerateEditButton="true" AutoGenerateColumns="false" Runat="server"> <Columns> <custom:ValidatedField DataField="Title" HeaderText="Movie Title" /> <custom:ValidatedField DataField="DateReleased" DataFormatString="{0:D}" HtmlEncode="false" ValidationDataType="Date" HeaderText="Date Released" /> <custom:ValidatedField DataField="BoxOfficeTotals" DataFormatString="{0:c}" HtmlEncode="false" ValidationDataType="Currency" HeaderText="Box Office Totals" /> </Columns> </asp:GridView> <asp:SqlDataSource ConnectionString="<%$ ConnectionStrings:Movies %>" SelectCommand="SELECT * FROM Movies" UpdateCommand="UPDATE Movies SET Title=@Title, DateReleased=@DateReleased, BoxOfficeTotals=@BoxOfficeTotals WHERE Id=@Id" Runat="server" /> </div> </form> </body> </html> | |