Introduction
This article chronicles my quest to both improve upon the existing ASP.NET list controls but also to learn about some of the hidden mysteries of Custom Web Controls.
Background
Custom Web Controls have been one of my favorite features of the ASP.NET framework from the time I first started using it three years ago. I had been doing web development for many years before that and the idea of being able to encapsulate my logic into controls was very appealing. The largest problem I kept running into when developing my own controls was the poor documentation on some of the advanced features. I looked at the simple controls I was developing, then I looked at rich controls such as the DataGrid
, and I noticed a large disparity. I found many articles on custom web controls and even read this book. Although these resources were helpful, they did not reveal the hidden secrets of the Microsoft web controls.
This article explains some of the things I learned while developing my own version of the ASP.NET list controls. I found the individual list items (RadioButton
and CheckBox
) to be very rich but when placed inside of their respective list controls (RadioButtonList
and CheckBoxList
) they list a lot of functionality. I wanted to be able to customize the styles for the individual list items and to have tooltips as well. I did, however, really like some of the features that the list controls afforded me such as data binding and layout control. When I discovered Lutz Roeder's Reflector, I decided to pick apart the Microsoft controls in an effort to write my own.
Notable Findings
Some of the code used in the ASP.NET list controls is marked as internal
so us lowly non-Microsoft developers do not have access to them. To do this, I simply copied their code and made it public
. There were some parts which were public but I had never seen documented. One such class is the interface System.Web.UI.WebControls.IRepeatInfoUser
. This class facilitates the layout control for any repeating list of items. The most notable part of the interface is:
public void RenderItem(ListItemType itemType, int repeatIndex,
RepeatInfo repeatInfo, HtmlTextWriter writer);
This code is called for each item in the list so that you can render each item yourself. Here is the complete code for implementing this method in my
RadioList
Control:
public void RenderItem(ListItemType itemType, int repeatIndex,
RepeatInfo repeatInfo, HtmlTextWriter writer)
{
RadioButton button1 = new RadioButton();
button1.Page = Page;
button1.GroupName = UniqueID;
button1.ID = string.Concat(ClientID, "_",
repeatIndex.ToString(NumberFormatInfo.InvariantInfo));
button1.Text = Items[repeatIndex].Text;
button1.ToolTip = Items[repeatIndex].ToolTip;
button1.Attributes["value"] = Items[repeatIndex].Value;
button1.Checked = Items[repeatIndex].Selected;
button1.TextAlign = TextAlign;
button1.AutoPostBack = AutoPostBack;
button1.TabIndex = radioButtonTabIndex;
button1.Enabled = Enabled;
foreach(string key in Items[repeatIndex].Attributes.Keys)
{
button1.Attributes[key] = Items[repeatIndex].Attributes[key];
}
button1.RenderControl(writer);
}
This code basically just using the information from the
Items
collection to build a
RadioButton
control and then to render it. It is that simple!
RichListItems
Most of the changes that I made were to the System.Web.UI.WebControls.ListItem
class. I made my own version called RichListItem
. I added a property of type System.Web.UI.WebControls.TableItemStyle
called Style
. This allows for each list item to have its own style settings. The attributes on this property are important when it comes to the behavior of this class being persisted when using the controls in design mode.
[
Category("Behavior"),
Description("The Style of this item"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.Attribute)
]
By using
PersistenceMode.Attribute
, the style information for each list item is persisted as attributes (using the '-' naming convention). The
DesignerSerializationVisibility.Content
is what tells Visual Studio to persist the styles as content.
Besides adding styles to the list items, I added the ToolTip
property. In order to do this, I had to change the ViewState
persistence to save the ToolTip
along with the Text
and the Value
of the list item.
internal object SaveViewState()
{
if(misc.Get(TEXTISDIRTY) && misc.Get(VALUEISDIRTY) && misc.Get(TOOLTIPISDIRTY))
{
return new Triplet(Text, Value, ToolTip);
}
else if (misc.Get(TEXTISDIRTY) && misc.Get(VALUEISDIRTY))
{
return new Pair(Text, Value);
}
else if (misc.Get(TEXTISDIRTY))
{
return Text;
}
else if (misc.Get(VALUEISDIRTY))
{
return new Pair(null, Value);
}
else if(misc.Get(TOOLTIPISDIRTY))
{
return new Pair(ToolTip, null);
}
return null;
}
internal void LoadViewState(object state)
{
Pair pair;
Triplet triplet;
if (state == null)
{
return;
}
if ((state as Triplet) != null)
{
triplet = ((Triplet) state);
Text = (string)triplet.First;
Value = (string)triplet.Second;
ToolTip = (string)triplet.Third;
}
else if ((state as Pair) != null)
{
pair = ((Pair) state);
if (pair.First != null)
{
if(pair.Second != null)
Text = (string)pair.First;
else
ToolTip = (string) pair.First;
}
if(pair.Second != null)
{
Value = ((string) pair.Second);
}
return;
}
this.Text = ((string)state);
}
An in depth explanation of state management is beyond the scope of this article but note how the
LoadViewState
and the
SaveViewState
methods compliment each other. In the case of the
RichListItem
, there are three possible values to be persisted:
Text
,
Value
, and
ToolTip
.
API Changes
There are some breaking changes as far as making the RichListControls backwards compatible with the ASP.NET list controls. There is still a base class, RichListControl
, which provides the basis for all list controls. I have added another level of inheritance in between the list controls and the base RichListControl
class. I broke them out into two different types: SingleSelectListControl
and MutipleSelectListControl
. This allows list controls which allow only one item to be selected to have properties for SelectedIndex
, SelectedItem
, SelectedValue
, and SelectedText
. List controls which allow multiple items to be selected have properties for SelectedIndices
, SelectedItems
, SelectedValues
, and SelectedTexts
. I have added a property to the base RichListControl
called HasSelection
which indicates whether or not any items are selected in the list. Here is an example of a possible use of these properties:
if(list.HasSelection)
{
foreach(RichListItem item in list.SelectedItems)
{
lblSelection.Text += String.Format(" {0}", item.Text);
}
}
else
{
lblSelection.Text = "No Selection";
}
Using the code
The RichListControl
s are used in the exact same way as the traditional ASP.NET list controls. Note the Style
and ToolTip
attributes below.
<%@ Register TagPrefix="List" Assembly="RichListControls"
Namespace="RichListControls" %>
<List:RichRadioButtonList id="list" runat="server">
<Items>
<list:RichListItem ToolTip="This is not that many miles"
Style-Font-Italic="True" Style-ForeColor="Green"
Value="0">0 - 15</list:RichListItem>
<list:RichListItem ToolTip="Most People Drive at least this much"
Value="5">15 - 30</list:RichListItem>
<list:RichListItem Style-Font-Bold="True" Style-ForeColor="Firebrick"
Style-BackColor="LightYellow"
Value="10">30+</list:RichListItem>
</Items>
</List:RichRadioButtonList>
Points of Interest
There are a couple of helper classes I used which are worth noting. First, there is StyleHelper
which is used by the RichDropDownList
to convert a System.Web.UI.WebControls.Style
to a string that can be used in an HTML style attribute. Most of this code is taken straight from internal methods in the System.Web dll.
There is also DataSourceHelper
which is used in databinding. All of the code in this is taken from the System.Web dll.
History
- 9/9/2004 - Initial release.