TextField: A DataControlField Example...


The BoundField control is inadequate for it’s text capabilities, and so the TextField control was created to serve that purpose. This article is an example to illustrate how you create a custom DataControlField control.
By: Brian Mains Spacer Date: September 12, 2006Spacer Download Spacer Download the code. Spacer Spacer Printer Friendly Version

The GridView and DetailsView controls support custom controls that inherit from the DataControlField class. These custom fields support inserting, editing, and viewing values in these controls without having to write your own custom databound controls to support them. Though the interface may not support a GUI way of adding custom DataControlField controls (at least in VWD), you can still use them by manually entering them in the HTML directly. Below is my example TextField class declaration in a GridView, with the "m:" prefix.

<asp:GridView ID="gvw" runat="server" DataSourceID="sds" AutoGenerateColumns="false" AutoGenerateDeleteButton="True" AutoGenerateEditButton="True" AutoGenerateSelectButton="True" DataKeyNames="CountryRegionCode">
  <Columns>
    <asp:BoundField HeaderText="Country/Region Code" DataField="CountryRegionCode" SortExpression="CountryRegionCode" />
    <m:textfield HeaderText="Name" DataField="Name" TextMode="MultiLine" Rows="2" Columns="30" MaximumLength="50" readonly="False" />
  </Columns>
</asp:GridView>

Why did I Create a TextField class? As of ASP.NET 2.0, the BoundField is inadequate for most needs. For instance, the BoundField represents a TextBox in edit/insert mode; however, you can't edit the control’s server properties. You can't specify whether it’s a single-row column or a multiple column textbox, how many rows/columns there are, or the maximum length of the textbox. I wanted to create a custom field with more flexibility and so I created this TextField control. It's actually pretty simple to implement; let’s look at the control in pieces. First, to initialize the cell, we come to the InitializeCell method, which overwrites the method to render the control-specific information in the cell.

public override void InitializeCell ( DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex )

{

    base.InitializeCell ( cell, cellType, rowState, rowIndex );

    Control control = null;

 

    if ( cellType == DataControlCellType.DataCell )

    {

        if ( this.IsReadMode ( rowState ) )

            control = cell;

        else

        {

            TextBox box = new TextBox ( );

            box.TextMode = this.TextMode;

            box.Rows = this.Rows;

            box.Columns = this.Columns;

            box.MaxLength = this.MaximumLength;

            box.ToolTip = this.HeaderText;

            cell.Controls.Add ( box );

 

            //If we have a data field, bind to the data binding event later

            if ( !string.IsNullOrEmpty ( this.DataField ) )

                control = box;

        }

    }

 

    if ( control != null && this.Visible )

        control.DataBinding += new EventHandler ( control_DataBinding );

}

Let's take a more detailed look at this method. First, we call the base method and create a local variable to store the reference to the control being bound. If the cell type is a data cell (not the header or footer), we have to render the appropriate control. In read-only mode, we get a reference to the cell, because we will write the text to the cell directly. In edit/insert mode, we instantiate the textbox, setting all of the appropriate properties, and add it to the cell. Only when the DataField property has been provided a value do we get a reference to the textbox, for databinding purposes. The last step, depending on the existence of the control and the visible property, taps into the DataBinding event for the control. So when the control is being databound to, this event fires, assigning the databound members.

The event handler for the databinding determines the type of the bound object. The code to retrieving the databinding specifics is the same though. The data item is retrieved by using the DataBinder helper method. The GetDataItem method returns the data item information from the naming container of the control. In this data item is the value for the specific DataField value, and as such, we get the value in string form. The third parameter, the format, is null because we don’t have a format to specify; we want the value directly. I use this overload because it returns a string value, whereas the two parameter method overload returns an object.

void control_DataBinding ( object sender, EventArgs e )

{

    object container = null;

 

    if ( sender is TableCell )

    {

        TableCell cell = sender as TableCell;

        object dataItem = DataBinder.GetDataItem ( cell.NamingContainer );

        cell.Text = DataBinder.GetPropertyValue ( dataItem, this.DataField, null );

    }

    else if ( sender is TextBox )

    {

        TextBox box = sender as TextBox;

        //If in insert mode, no text should appear

        if ( !this.InsertMode )

        {

            object dataItem = DataBinder.GetDataItem ( box.NamingContainer );

            box.Text = DataBinder.GetPropertyValue ( dataItem, this.DataField, null );

        }

    }

}

The last method, ExtractValuesFromCell, is called by the GridView/DetailsView ExtractRowValues method, which extracts the value from the TextBox, provided the value exists. If it doesn't, a null value is used. Esentially, the value (or null) is extracted so it can be added as a new entry in the dictionary, with the DataField property (the field that the control is bound to) as the key. A value could exist in this entry, so we must ensure it isn't added again (an error may be thrown).

public override void ExtractValuesFromCell(System.Collections.Specialized.IOrderedDictionary dictionary, DataControlFieldCell cell, DataControlRowState rowState, bool includeReadOnly)

{

    base.ExtractValuesFromCell(dictionary, cell, rowState, includeReadOnly);

    string value = null;

 

    if (cell.Controls.Count > 0)

    {

        Control control = cell.Controls[index];

        if (control == null)

            throw new InvalidOperationException("The control cannot be extracted");

        value = ((TextBox)control).Text;

    }

 

    if (dictionary.Contains(this.DataField))

        dictionary[this.DataField] = value;

    else

        dictionary.Add(this.DataField, value);

}

In the code attached, I hooked the GridView above up to a SqlDataSource control connected to the AdventureWorks database. The table pulls back two fields, of which the second field has the TextMode property set to Multiline, with 2 Rows, 30 Columns, and a MaximumLength value of 50. The final value looks like this when editing:

TestField

It's actually really simple to construct a custom DataControlField, and is very beneficial to do so. You can add even more complex logic hooked into various property values, which creates more functionality in the GridView or DetailsView itself. And it is very easy to accomplish.