Customizing RadioButtonList
This was asked on forums and result was quite weird control but I post it here for demonstration purposes, nevertheless. :-)
User needed to customize RadioButtonList so that he doesn't need to use RadioButton. He wants to show a DIV or SPAN (probably some image icon in that too). Issue was that overriding RenderItem breaks the postback handling logic since RadioButtonList uses internally RadioButton controls. It means that the postback data (this case relevant thing is selected ListItem's value) isn't routed to the server, so the "underlying" RadioButtonList doesn't know about the change in selection (raise SelectedIndexChanged event).
Basically this needs to be addressed with mechanism to pass the information about the selected ListItem's value to the server, and there pass that to the base IPostBackDataHandler implementation. Here's the code (if you want to see it in color, see the forum post )
public class MyRadioButtonList : RadioButtonList
{
private string HiddenValueFieldName
{
get
{
return this.ClientID + "_theValue";
}
}
private string DivArrayName
{
get
{
return this.ClientID + "_theDivs";
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string script = "";
script += "function setValue(theDiv){";
script += "document.getElementById('" + HiddenValueFieldName + "').value = theDiv.getAttribute('value');";
if(this.AutoPostBack)
script += Page.ClientScript.GetPostBackEventReference(this, "", false);
else{
script += "theDiv.style.backgroundColor='gray';";
script += "for(i=0;i<" + DivArrayName + ".length;i++){";
script += "var theVal=" + DivArrayName + "[i];";
script += "if(theDiv.getAttribute('id') != theVal)";
script += "document.getElementById(theVal).style.backgroundColor='white';";
script += "}";
}
script += "}";
Page.ClientScript.RegisterStartupScript(this.GetType(),"setValue",script,true);
//register the hidden field
Page.ClientScript.RegisterHiddenField(HiddenValueFieldName, "");
//register that LoadPostdata will be called even if control's UniqueID is not on post data collection
Page.RegisterRequiresPostBack(this);
//this is playing for the client-side color selection :-)
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach(ListItem litem in this.Items)
{
sb.Append("'");
sb.Append(this.ClientID);
sb.Append(this.IdSeparator);
sb.Append(litem.Value);
sb.Append("'");
sb.Append(",");
}
string s = sb.ToString().TrimEnd(',');
Page.ClientScript.RegisterArrayDeclaration(this.ClientID + "_theDivs", s);
}
protected override bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
//wrap the existing loading logic to get the LiostItem value from our hidden field
return base.LoadPostData(HiddenValueFieldName, postCollection);
}
protected override void RenderItem(ListItemType itemType, int repeatIndex, RepeatInfo repeatInfo, HtmlTextWriter writer)
{
//render the thing out as a DIV
ListItem item=this.Items[repeatIndex];
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px");
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "black");
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid");
writer.AddAttribute(HtmlTextWriterAttribute.Value, item.Value);
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "setValue(this)");
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.UniqueID + this.IdSeparator + item.Value);
if (item.Selected)
writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, "gray");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Write(item.Text);
writer.RenderEndTag();
}
}
Yes, this can also be easier if you want total control of the thing, just derive from ListControl and write it from scratch. Here we got radioButtonList's logic to reuse at some level, so it was the worth (plus forum user wanted that :-) )