Creating a Custom Data Field Control - Part 2
 
Published: 17 Jan 2008
Abstract
The second article in this series shows you more details about building custom data field controls, including some examples the author has built. This article introduces another level of reusability for building simple data field controls.
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 28652/ 56

Introduction

In my previous article I wrote about the basics of developing data control fields that can be used in the GridView or DetailsView field. This article will show some more concrete examples, using a more simplified approach by creating a custom base class that makes developing custom data control fields even easier.

The Custom Base Class

Previously, I illustrated code samples taken from a primitive base class I created for working with the essential functions with data control fields. However useful that was, I figured that for a certain percentage of situations I create data fields in, another level of abstraction is handy. The following is a second base class that I created (called the BaseSingleDataField), another level of abstraction is helpful to have. The methods that are abstract in this base class are illustrated below; each one has to perform a specific action.  I will discuss each one in detail.

Listing 1

public abstract Control SetupEditControl();

This method creates an instance of the control that will edit the value in insert or edit mode. This assumes that there is one instance of a control that performs the editing. It is possible to work with more, but the edited control has to be at the zero index mark.

Listing 2

public abstract string GetEditControlValue(TableCell cell);

The control representing the insert/edit interface has a value, which will be used to update the dictionary with the new value from it. However, the base class does not know how to get a value from the edit interface control, so this method is in charge of handling, extracting that value and providing it to the base class, which uses it to add the value to the dictionary.

Listing 3

public abstract void BindEditControl(object control, bool insertMode);

The data, whatever that may be, needs bound to the control. An insert mode flag determines whether the control is currently in insert mode; the row state enumeration is not needed because this only works whenever in edit mode, and read only mode is not an option. So, this method is in charge of binding the edit control with the corresponding data field value. It is up to the method to determine what field that is, and how to get the value.

Listing 4

protected abstract string GetDataItemFieldName(DataControlRowState state);

Although most data fields have a single DataField property, I did not want that to be a limitation set in the primitive base class. So, as an alternative, this class requires the overriding of a GetDataItemFieldName method that returns the name of the field to bind with. The row state is also passed because it is possible to have multiple field names, one for each state (you never know how useful that could be).

Essentially, the basic actions are the editing interface gets setup, gets bound, and returns its value. There is some additional functionality for read-only mode illustrated later.

Added Functionality

The code below shows the actual implementation of the BaseSingleDataField class, which handles some of the basic functionality when interacting with a single and simple interface data field.  First, the control's interface is created in the InitializeCell method. This method makes the determination based on the current cell type, whether in read-only mode or in edit/insert mode.  Take a look below.

Listing 5

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
    {
      cell.Controls.Add(this.SetupEditControl());
 
      if (!string.IsNullOrEmpty(this.GetDataItemFieldName(rowState)))
        control = cell.Controls[0];
    }
 
    if (control != null && this.Visible)
      control.DataBinding += new EventHandler(control_DataBinding);
  }
}

At the beginning of this method is a reference to a control variable. If in read-only mode, this variable then snags the reference to the table cell (which the value is entered directly into the cell).  If in edit or insert mode, the SetupEditControl method creates a control that will represent the edit/insert interface for the underlying data item.

At the end, if a control reference was successfully created and this field is visible, the control creates an event handler for data binding. The purpose of tapping into this event is to perform the binding to the actual control. If in read-only mode, the sender control reference will be the table cell; otherwise, it will match the edited control returned from SetupEditControl.

Listing 6

void control_DataBinding(object sender, EventArgs e)
{
  if (sender is TableCell)
  {
    TableCell cell = sender as TableCell;
    cell.Text = this.GetReadOnlyValue(this.GetDataItemValue
      (cell.NamingContainer, this.GetDataItemFieldName
      (DataControlRowState.Normal)));
  }
  else
    this.BindEditControl(sender, this.InsertMode);
}

Note that this method uses certain primitive base class methods, notably the GetReadOnlyValue, GetDataItemValue, and the GetDataItemFieldName methods. You can find these in the code sample attached. The next method extracts the values back out of the interface controls, and adds it to the dictionary.

In the GridView and DetailsView controls, a dictionary contains the field names and their corresponding values. When updating, both the old and new values are tracked, and insertions only track the new values. These dictionaries store the values that the data source control can utilize. Below is the method to get the value from the editing control and add it to the dictionary.

Listing 7

public override void ExtractValuesFromCell(IOrderedDictionary dictionary,
  DataControlFieldCell cell, DataControlRowState rowState, bool includeReadOnly)
{
  base.ExtractValuesFromCell(dictionary, cell, rowState, includeReadOnly);
  string value = null;
 
  if (cell.Controls.Count > 0)
    value = this.GetEditControlValue(cell);
 
  string dataField = this.GetDataItemFieldName();
 
  if (dictionary.Contains(dataField))
    dictionary[dataField] = value;
  else
    dictionary.Add(dataField, value);
}

If you look at the code sample, the code to add/insert the item in the dictionary has been extracted to an AddDictionaryEntry method.

The DateTimeField Example

The following examples are consumers of this custom data field class described above. The first one is a field that represents date/time values. In read-only mode, it renders the date in the cell.  In edit mode, depending on a value of the Display property, it renders with a calendar or a textbox. This control binds a single value, a date, through the DataField property.

To setup the control, the SetupEditControl appears below.

Listing 8

public override Control SetupEditControl()
{
  if (this.Display == CalendarDataFieldDisplay.Calendar)
  {
    Calendar cal = new Calendar();
    cal.SelectedDate = DateTime.Today;
    return cal;
  }
  else if (this.Display == CalendarDataFieldDisplay.CalendarPopup)
  {
    return new DateTextBoxSelector();
  }
  else if (this.Display == CalendarDataFieldDisplay.Editor)
  {
    return new DateTextBox();
  }
  else
    throw new InvalidOperationException();
}

Depending on the value of the Display property, the respective control reference is returned.  Notice that some controls need some setting up with default values, and for most it does not matter. This control is added to the cell's control collection (shown in the implementation above).  Below is the method that extracts the value out of the control returned above.

Listing 9

public override string GetEditControlValue(TableCell cell)
{
  if (this.Display == CalendarDataFieldDisplay.Calendar)
    return this.ExtractControl < Calendar > (cell)
      .SelectedDate.ToShortDateString();
  else if (this.Display == CalendarDataFieldDisplay.CalendarPopup)
    return this.ExtractControl < DateTextBoxSelector > (cell).Text;
  else if (this.Display == CalendarDataFieldDisplay.Editor)
    return this.ExtractControl < DateTextBox > (cell).Text;
  else
    return null;
}

The GetEditControlValue method is in charge of getting a reference of the editing control, and returns the edited value in a string format. Because the primitive class cannot automatically determine what control is actually representing the interface, this method makes that determination based on the enumerated value. The ExtractControl method finds the control reference by index, and returns the reference in a strongly-typed fashion.

When in edit or insert mode, the BindEditControl method binds the control with data in the underlying data source. The following is the approach in this example.

Listing 10

public override void BindEditControl(object control, bool insertMode)
{
  if (this.Display == CalendarDataFieldDisplay.Calendar)
  {
    Calendar cal = (Calendar)control;
    if (!insertMode)
      cal.SelectedDate = this.GetDataItemValue < DateTime >
        (cal.NamingContainer, this.DataField);
    else
      cal.SelectedDate = DateTime.Today;
  }
  else if (this.Display == CalendarDataFieldDisplay.CalendarPopup)
  {
    DateTextBoxSelector selector = (DateTextBoxSelector)control;
    if (!insertMode)
      selector.Value = this.GetDataItemValue < DateTime >
        (selector.NamingContainer, this.DataField);
  }
  else if (this.Display == CalendarDataFieldDisplay.Editor)
  {
    DateTextBox box = (DateTextBox)control;
    if (!insertMode)
      box.Value = this.GetDataItemValue < DateTime > (box.NamingContainer,
        this.DataField);
  }
}

When in edit mode, the GetDataItemValue method gets the actual value from the data source; otherwise, insert mode renders an empty (or default) value. Because an enumeration determines which control is created as the editing interface, a more winded approach is required in this situation. But you can see it is still a simplified approach in comparison.

The last method defined, not mentioned much above, is GetDataItemFieldName, which seems redundant or unnecessary. However, in the final implementation, I did not want to force someone to use a DataField property directly; instead, this allows flexibility to use a different name like DataTextField, DataSelectedValueField, or something else. What this means is that even though multiple fields can affect the interface, only one field represents the actual underlying data source.

Listing 11

protected override string GetDataItemFieldName()
{
  return this.DataField;
}
The BooleanField Example

This example contains a similar approach to the DateTimeField mentioned above. It is actually simpler to implement because it does not have many challenges. For instance, here is the definition of the previous four methods as shown above.

Listing 12

public override void BindEditControl(object control, bool insertMode)
{
  CheckBox box = control as CheckBox;
  if (!insertMode)
    box.Checked = this.GetDataItemValue < bool > (box.NamingContainer,
      this.DataField);
}
 
protected override string GetDataItemFieldName()
{
  return this.DataField;
}
 
public override string GetEditControlValue(TableCell cell)
{
  return this.ExtractControl < CheckBox > (cell).Checked.ToString();
}
 
public override Control SetupEditControl()
{
  return new CheckBox();
}

Because the control is a checkbox, which the interface is not manipulated by internal properties, it is easy to handle the interactions. However, the CheckBoxField implements some of the additional functionality available. For instance, this custom class defines a TrueString and FalseString property values which convert the read-only value to something more appropriate.

To do this, it requires catching the value being bound to the table cell, and outputting a different value. I created a GetReadOnlyValue method that changes the initial value into something else, all at your control. The implementation that works with this custom field is shown below (note that the default to TrueString and FalseString are the bool.TrueString and bool.FalseString properties).

Listing 13

protected override string GetReadOnlyValue(object initialValue)
{
  if (initialValue == null)
    return bool.FalseString;
 
  bool value = (bool)initialValue;
  return value ? this.TrueString : this.FalseString;
}
Conclusion

It can be handy to use abstraction to reduce the amount of work that you have to do to create a custom data field control. This article shows you how you can do so, rather easily. Custom data fields save you from having to perform a lot of work. I am continually working on making custom data field development easier, which you can find my latest work in the Nucleo Framework. I recently released the components so you can find the latest release at the above link. You can use the tool Reflector to view the source code.



User Comments

Title: Article   
Name: bhaskar
Date: 2008-08-30 6:23:21 AM
Comment:
good






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2021 ASPAlliance.com  |  Page Processed at 2021-11-29 7:19:54 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search