I am going to move onto a base class I created to help with
some of the basic interactions in creating custom data control fields.
The first method overrides ExtractValuesFromCell, which does
not have a lot of use in a base class. This is because the real meaning of this
method is to extract the underlying values of the editing control and provide
them to the data source, which none of this is known yet (exposed by the
consumer). The following implementation simply determines if the rendering is
in insert mode.
Listing 8
public override void ExtractValuesFromCell(IOrderedDictionary dictionary,
DataControlFieldCell cell, DataControlRowState rowState, bool includeReadOnly)
{
//base.ExtractValuesFromCell(dictionary, cell, rowState, includeReadOnly);
_insertMode = (rowState == DataControlRowState.Insert);
}
The next method that is helpful extracts an instance of a
control from within the cell, making it easier to get a strongly-typed
reference. It performs checking to make sure that the control really does exist
at the specified cell index.
Listing 9
protected T ExtractControl<T>(TableCell cell, int controlIndex) where T : Control
{
if (cell == null)
throw new ArgumentNullException("cell");
if (controlIndex < 0 || controlIndex >= cell.Controls.Count)
throw new ArgumentOutOfRangeException("index", Errors.OUT_OF_RANGE);
Control control = cell.Controls[controlIndex];
if (control == null)
throw new InvalidOperationException(Errors.CANT_EXTRACT_CONTROL);
return (T)control;
}
The next method is very helpful in reflecting against the
underlying data type, and pulling out the real value. Note that the DataBinder
method is used, so the method does not use a new approach to extracting the
values, but compacts the approach to retrieve the underlying value.
Listing 10
protected object GetDataItemValue(object container, string propertyName)
{
if (container == null || this.DesignMode)
return null;
//Get the data item from the container
object dataItem = DataBinder.GetDataItem(container);
if (dataItem == null) return null;
object value = null;
if (!propertyName.Contains("."))
//Get the value from the data item
value = DataBinder.GetPropertyValue(dataItem, propertyName);
else
{
value = dataItem;
string[] propertyList = propertyName.Split('.');
foreach (string propertyItem in propertyList)
{
value = DataBinder.GetPropertyValue(value, propertyItem);
if (value == null) return null;
}
}
return value;
}
The next method performs read-only formatting, making it
easier to alter the read-only value that is rendered in the cell. Though in the
base class there is not any meaning to this yet, it comes in handy later. For
instance, for a numeric field I provided two properties called Prefix and
Suffix, which append that text to the value itself. So, I could append a
"$" and a ".00" to make a field look like a whole number
monetary amount.
Listing 11
protected virtual string GetReadOnlyValue(object initialValue)
{
if (initialValue != null)
return initialValue.ToString();
else
return null;
}
For some reason, the data control field base class requires
that the field be created through the CreateField method. Why not use
reflection and handle this automatically? The following method does just that.
Listing 12
protected override DataControlField CreateField()
{
return (DataControlField)Activator.CreateInstance(this.GetType());
}
In addition, the code that adds a data item to the
underlying dictionary (which stores all the values that will be passed to the
underlying data source) is simple code, but it helps not to have to repeat it
over and over. Below is the code to check the dictionary for the existence of a
dictionary entry by the name, and the appropriate method call to update it.
Listing 13
protected virtual void AddDictionaryEntry(IOrderedDictionary dictionary,
string dataField, object value)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (string.IsNullOrEmpty(dataField))
throw new ArgumentNullException("dataField");
if (dictionary.Contains(dataField))
dictionary[dataField] = value;
else
dictionary.Add(dataField, value);
}