Custom DataControlField containing DropDownList
While the idea is far from new (and can be seen in various implementations here, here, and here), every example I had found fell short of being exactly what I was looking for. My requirements, briefly, were:
- Call a custom DataControlField from within a DetailsView
- Do so from the ASP, not programmatically in the code-behind
- Display a Label in View mode, but display a DropDownList in Edit Mode
- The DropDownList must be tied to a SQLDataSource
- The Update event, clearly, should save the selected value
- Intellisense Integration. Because it gives me warm fuzzies.
Why?
Among the many variations uncovered above, one plaguing question kept arising – Why? This can easily be done by simply using a TemplateField. My answer is: Frequency.
This is not a once-in-a-lifetime requirement. I use this functionality – a lot. And while I appreciate the flexibility of the TemplateField, it is kind of annoying to me that I have to keep re-writing the same lines of code over-and-over again every single page I want to slap a DropDownList in a DetailsView. The DRY (Don’t Repeat Yourself) principle forced me over the edge.
Introducing… The DropDownField
While I expect to be revising both this page and the code as it is used out in the wild, I’ll be keeping this page updated both with my findings and any feedback I received.
The ASP Code
This run-of-the-mill DetailsView is bound to a SQLDataSource, as is the source for the DropDownList.
<res:DropDownField HeaderText=”Status” DataSourceID=”StatusData” DataTextField=”status” DataValueField=”status” />
The prefix for the control is “res”, which is defined in the web.config.
<system.web>
…
<pages>
<controls>
<add assembly=”JoeDonahue” namespace=”JoeDonahue.resources” tagPrefix=”res” />
</controls>
</pages>
</system.web>
DropDownField.cs
- revision 2 – Updated 11/30/2011
- revision 3 – Updated 07/26/2012
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;namespace JoeDonahue.resources {
public class DropDownField : DataControlField {public String DataTextField {
get {
object dataTextField = ViewState[“DataTextField”];
if (dataTextField != null) {
return dataTextField.ToString();
}
return string.Empty;
}
set {
this.ViewState[“DataTextField”] = value;
}
}public string DataValueField {
get {
object dataValueField = ViewState[“DataValueField”];
if (dataValueField != null) {
return dataValueField.ToString();
}
return string.Empty;
}
set {
this.ViewState[“DataValueField”] = value;
}
}public String DataSourceID {
get {
object dataSourceID = ViewState[“DataSourceID”];
if (dataSourceID != null) {
return dataSourceID.ToString();
}
return string.Empty;
}
set {
this.ViewState[“DataSourceID”] = value;
}
}public string SelectedValue {
get {
object selectedValue = ViewState[“selectedValue”];
if (selectedValue != null) {
return selectedValue.ToString();
}
return string.Empty;
}
set {
this.ViewState[“selectedValue”] = value;
}
}private Boolean inEditMode;
/** Methods **/
protected override DataControlField CreateField() {
return new DropDownField();
}public override void InitializeCell(DataControlFieldCell cell,
DataControlCellType cellType, DataControlRowState rowState, int rowIndex) {
base.InitializeCell(cell, cellType, rowState, rowIndex);switch (cellType) {
case DataControlCellType.DataCell:
this.InitializeDataCell(cell, rowState);
break;
case DataControlCellType.Footer:
this.InitializeFooterCell(cell, rowState);
break;
case DataControlCellType.Header:
this.InitializeHeaderCell(cell, rowState);
break;
}
}protected void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState) {
cell.DataBinding += new EventHandler(OnDataBindField);
this.inEditMode = (rowState & (DataControlRowState.Edit | DataControlRowState.Insert)) != 0;
if (inEditMode) {
DropDownList ddl = new DropDownList();
ddl.ID = this.DataTextField;
ddl.DataSourceID = this.DataSourceID;
ddl.DataTextField = this.DataTextField;
ddl.DataValueField = this.DataValueField;
cell.Controls.Add(ddl);
} else {
Label l = new Label();
cell.Controls.Add(l);
}
}protected void InitializeHeaderCell(DataControlFieldCell cell, DataControlRowState rowState) {
}protected void InitializeFooterCell(DataControlFieldCell cell, DataControlRowState rowState) {
}protected virtual void OnDataBindField(object sender, EventArgs e) {
TableCell cell = (TableCell)sender;
IDataItemContainer container = (IDataItemContainer)cell.NamingContainer;
object boundvalue = GetBoundValue(container);
String selectedValue = boundvalue.ToString();/* Set the corresponding text or selectedValue */
if (inEditMode) {
object dataItem = DataBinder.GetDataItem(container);
selectedValue = DataBinder.GetPropertyValue(dataItem, DataValueField).ToString();
DropDownList ddl = (DropDownList)cell.Controls[0];
ddl.SelectedValue = selectedValue;
} else {
Label l = (Label)cell.Controls[0];
l.Text = selectedValue;
}}
object GetBoundValue(IDataItemContainer controlContainer) {
object dataItem = DataBinder.GetDataItem(controlContainer);
return DataBinder.GetPropertyValue(dataItem, DataTextField);
}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[0];
if (control == null)
throw new InvalidOperationException(“The control cannot be extracted”);
value = ((DropDownList)control).SelectedValue;
}if (dictionary.Contains(this.DataValueField))
dictionary[this.DataValueField] = value;
else
dictionary.Add(this.DataValueField, value);
}
}
}
Helpful Resources
The following links were really helpful in accomplishing this. Thanks!
Hi Joe;
Excellent article, I was able to get it working in VB, but sure how to get design time support working on this control. The designer will not recognize the extra fields that are coming from the new control. Where can i find some pointers on solving this. Any help is appreciated. Thanks