Binding List<CompositeControl> to a Repeater - c#

I have a collection of generated custom controls that extend CompositeControl, defined as:
[PersistChildren(true)]
[ToolboxData("<{0}:ContractControl runat=server></{0}:ContractControl>")]
public class ContractControl : CompositeControl
{
private int contractID = 0;
private ContractTileControl tileControl = null;
private ContractDetailControl detailControl = null;
private HtmlGenericControl contractMainDiv = null;
public int ContractID
{
get { return this.contractID; }
set { this.contractID = value; }
}
public ContractTileControl TileControl
{
get { return this.tileControl; }
set { this.tileControl = value; }
}
public ContractDetailControl DetailControl
{
get { return this.detailControl; }
set { this.detailControl = value; }
}
public ContractControl()
{
this.contractMainDiv = new HtmlGenericControl("div");
this.contractMainDiv.ID = "contractMainDiv";
this.contractMainDiv.Attributes.Add("class", "contractMain");
}
#region protected override void OnPreRender(EventArgs e)
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
//CreateChildControls();
}
#endregion
#region protected override void CreateChildControls()
protected override void CreateChildControls()
{
base.CreateChildControls();
if (tileControl != null)
{
this.contractMainDiv.Controls.Add(tileControl);
}
if (detailControl != null)
{
this.contractMainDiv.Controls.Add(detailControl);
}
this.Controls.Add(contractMainDiv);
//base.CreateChildControls();
}
#endregion
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
CreateChildControls();
}
protected override void OnInit(EventArgs e)
{
base.OnLoad(e);
EnsureChildControls();
}
}
Where ContractTileControl and ContractDetailControl are another custom controls derived from CompositeControl.
When I add them to a asp:PlaceHolder control set they render fine, but when I define a repeater like:
<asp:Repeater ID="myRepeater" runat="server" >
<HeaderTemplate>
<table border="0" cellpadding="0" cellspacing="0">
</HeaderTemplate>
<ItemTemplate>
<tr><td><easit:ContractControl ID="contractControl" runat="server" />
</td></tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
And bind them to it:
private void FillContractPlaceHolder()
{
List<ContractControl> controls = new List<ContractControl>();
foreach(KeyValuePair<Customer, List<TFSContract>> pair in contractList)
{
Label customerNameLbl = new Label();
customerNameLbl.ID = "customerNameLbl";
customerNameLbl.CssClass = "customerName";
customerNameLbl.Text = pair.Key.Name;
contractListPlaceHolder.Controls.Add(customerNameLbl);
foreach (TFSContract contract in pair.Value)
{
ContractStatusBarControl status = new ContractStatusBarControl();
status.WidthPercent = GetFillPercent(contract.NumberOfTasks, contract.NumberOfFinishedTasks);
string[] contractNameParts = Regex.Split(contract.Contract.Name, #"[A-Z]{3}-[0-9|A-Z]{2}-[0-9|A-Z]{2}", RegexOptions.IgnoreCase);
ContractDetailControl detail = new ContractDetailControl();
detail.ContractName = contractNameParts.Last();
detail.DateStarted = contract.StartDate;
detail.DateFinished = contract.FinishDate;
detail.StatusBar = status;
ContractTileControl tile = new ContractTileControl();
Match match = Regex.Match(contract.Contract.Name, #"[A-Z]{3}-[0-9|A-Z]{2}-[0-9|A-Z]{2}", RegexOptions.IgnoreCase);
if (match.Value.Length != 0)
{
tile.ContractNumber = match.Value;
}
tile.ContractTasksFinished = contract.NumberOfFinishedTasks;
tile.ContractTasksTotal = contract.NumberOfTasks;
ContractControl contractControl = new ContractControl();
contractControl.ContractID = contract.Contract.Id;
contractControl.TileControl = tile;
contractControl.DetailControl = detail;
//contractListPlaceHolder.Controls.Add(contractControl);
controls.Add(contractControl);
}
}
myRepeater.DataSource = controls;
myRepeater.DataBind();
}
The table gets created, but only the non-composite part contractMainDiv of ContractControl gets rendered, as the Repeater insists that both tileControl and detailControl are null, even though they are properly set to instances of their respective types.

When the Repeater is data-bound, it creates an instance of the ItemTemplate for each item in the data-source, set its DataItem to the item from the data-source, and data-binds the children.
In this case, the item from the data-source is an instance of your ContractControl, and your ItemTemplate has no data-binding, so you'll end up with a blank instance of the ContractControl for each item you've added to the list.
The quick and dirty solution is to add a handler for the ItemDataBound event of your Repeater, and copy the properties to the real control:
protected void myRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
switch (e.Item.ItemType)
{
case ListItemType.Item:
case ListItemType.AlternatingItem:
case ListItemType.SelectedItem:
case ListItemType.EditItem:
{
var source = (ContractControl)e.Item.DataItem;
var destination = (ContractControl)e.Item.FindControl("contractControl");
destination.ContractID = source.ContractID;
destination.TileControl = source.TileControl;
destination.DetailControl = source.DetailControl;
break;
}
}
}
A better solution would be to bind your Repeater to a list of TFSContract objects, and moving the code to build the ContractControl into the ItemDataBound event handler.
EDIT
Updated to only process real items, ignoring headers, footers, etc.

Related

Rendering child controls of custom composite control inside a Repeater

I have a custom control derived from the CompositeControl class, which is defined as:
[ToolboxData("<{0}:ContractControl runat=server></{0}:ContractControl>")]
public class ContractControl : CompositeControl
{
private int contractID = 0;
private ContractTileControl tileControl = null;
private ContractDetailControl detailControl = null;
private HtmlGenericControl contractMainDiv = null;
public int ContractID
{
get { return this.contractID; }
set { this.contractID = value; }
}
public ContractTileControl TileControl
{
get { return this.tileControl; }
set { this.tileControl = value; }
}
public ContractDetailControl DetailControl
{
get { return this.detailControl; }
set { this.detailControl = value; }
}
public ContractControl()
{
this.contractMainDiv = new HtmlGenericControl("div");
this.contractMainDiv.ID = "contractMainDiv";
this.contractMainDiv.Attributes.Add("class", "contractMain");
}
#region protected override void OnPreRender(EventArgs e)
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
CreateChildControls();
}
#endregion
#region protected override void CreateChildControls()
protected override void CreateChildControls()
{
if (tileControl != null)
{
this.contractMainDiv.Controls.Add(tileControl);
}
if (detailControl != null)
{
this.contractMainDiv.Controls.Add(detailControl);
}
this.Controls.Add(contractMainDiv);
}
#endregion
}
When I add a number of them to a placeholder control they render fine, but when I try to bind the same ones to a Repeater, the child composite controls tileControl and detailControl do not render, only the contractMainDiv does.
The repeater is defined as:
<asp:Repeater ID="myRepeater" runat="server" EnableTheming="true">
<HeaderTemplate>
<table border="0" cellpadding="0" cellspacing="0">
</HeaderTemplate>
<ItemTemplate>
<tr><td><easit:ContractControl ID="contractControl" runat="server" />
</td></tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
And I bind to it by first generating a List<ContractControl> and then calling:
List<ContractControl> controls = new List<ContractControl>();
//Generate custom controls for each element in input dictionary
//Create TileControl and DetailControl
//Create ContractControl and add it to collection
ContractControl contractControl = new ContractControl();
contractControl.ContractID = contract.Contract.Id;
contractControl.TileControl = tile;
contractControl.DetailControl = detail;
controls.Add(contractControl);
myRepeater.DataSource = controls;
myRepeater.DataBind();
Yet the resulting table contains the right number of items, but the child 'CompositeControl's do not get rendered at all, only the contractMainDiv shows up.

Creating custom TreeView/TreeNode

I need to extend the TreeNode class such that I can add custom properties to each node (seeing as WebForms TreeNode doesn't include the Tag property). So this is my CustomTreeNode:
public class CustomTreeNode : TreeNode
{
public CustomTreeNode()
{
}
public CustomTreeNode(int nodeId, string nodeType)
{
NodeId = nodeId;
NodeType = nodeType;
}
public string NodeType { get; set; }
public int NodeId { get; set; }
}
If I create a CustomTreeNode and add it to a TreeView:
CustomTreeNode node = new CustomTreeNode(1, "CustomType");
treeView.Nodes.Add(node);
I would then get a casting exception doing the following:
CustomTreeNode selectedNode = (CustomTreeNode)TreeView.SelectedNode;
because TreeView returns a TreeNode, not a CustomTreeNode.
I've done some reading, and it looks like I need to extend the TreeView class, and override the CreateNode() method to return CustomTreeNode instead of TreeNode. So I created this:
public class CustomTreeView : TreeView
{
protected override TreeNode CreateNode()
{
return new CustomTreeNode();
}
}
The problem is however, CreateNode() doesn't take any arguments, so you have to have call the empty constructor for the CustomTreeNode class. So when I created my CustomTreeNode above, when I get it back from the CustomTreeView, the nodeId and nodeType values have been lost because the empty constructor returns a node without any values.
Any help much appreciated.
This is what I came up with (experts, any advice welcomed). Instantiate the CustomTreeNodes in your code behind and set the properties via setters. Modify your CustomTreeNode class to persist the values in ViewState. The node returned by your custom tree view's CreateNode will load the ViewState information.
TreeNode class:
[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomTreeNode runat=server></{0}:CustomTreeNode>")]
public class CustomTreeNode : TreeNode
{
private const int NODE_TYPE = 1;
private const int NODE_ID = 2;
public string NodeType { get; set; }
public int NodeId { get; set; }
protected override void LoadViewState(Object savedState)
{
if (savedState != null)
{
object[] myState = (object[])savedState;
if (myState[0] != null)
base.LoadViewState(myState[0]);
if (myState[NODE_TYPE] != null)
this.NodeType = (string)myState[NODE_TYPE];
if (myState[NODE_ID] != null)
this.NodeId = (int)myState[NODE_ID];
}
}
protected override Object SaveViewState()
{
object baseState = base.SaveViewState();
object[] allStates = new object[3];
allStates[0] = baseState;
allStates[NODE_TYPE] = this.NodeType;
allStates[NODE_ID] = this.NodeId;
return allStates;
}
}
TreeView class:
[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomTreeView runat=server></{0}:CustomTreeView>")]
public class CustomTreeView : TreeView
{
protected override TreeNode CreateNode()
{
// Tree node will get its members populated with the data from VIEWSTATE
return new CustomTreeNode();
}
}
Simple .aspx file (Assuming that your custom control is defined in an assembly "Foo" and a namespace "Bar":
<%# Register TagPrefix="customControl" Assembly="Foo" Namespace="Bar" %>
<customControl:CustomTreeView ID="sampleTree"
runat="server" onselectednodechanged="sampleTree_SelectedNodeChanged"></customControl:CustomTreeView>
<asp:Label ID="lblSelectedNode" runat="server" ></asp:Label>
CodeBehind:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
PopulateTree();
}
}
private void PopulateTree()
{
sampleTree.Nodes.Clear();
CustomTreeNode root = new CustomTreeNode();
root.Value = "root node";
sampleTree.Nodes.Add(root);
// Creating some fake nodes (you would of course be using real data)
for (int i = 0; i < 10; i++)
{
CustomTreeNode child = new CustomTreeNode();
child.NodeId = i; // Saved in ViewState
child.NodeType = "Type " + i; // Saved in ViewState
child.Value = child.NodeType;
root.ChildNodes.Add(child);
}
}
protected void sampleTree_SelectedNodeChanged(object sender, EventArgs e)
{
CustomTreeView cTreeView = (CustomTreeView) sender;
lblSelectedNode.Text = ((CustomTreeNode)cTreeView.SelectedNode).NodeType;
}

Storing Viewstate in Database disables my asp:ImageButtons events

I followed the guide here to save my ViewState into the database. I did some changes to it to fit my projects code design, but the essential parts are there. But when I implant this solution all my asp:ImageButtons events stops working, but regular asp:Buttons seems to work. Why doesn't the events from asp:ImageButtons work?
Code:
public class DatabasePageStatePersister : PageStatePersister
{
//This object handles the saving and loading from database
CiroLightLibrary.BLL.ViewState myViewState;
public DatabasePageStatePersister(Page p, string GUID): base(p)
{
myViewState = new CiroLightLibrary.BLL.ViewState();
myViewState.GUID = GUID;
}
public override void Load()
{
myViewState.Load();
this.ViewState = this.StateFormatter.Deserialize(myViewState.Value);
}
public override void Save()
{
myViewState.Value = this.StateFormatter.Serialize(this.ViewState);
myViewState.Save();
}
}
public class PageViewStateDatabaseStored : Page
{
private PageStatePersister _PageStatePersister;
protected override System.Web.UI.PageStatePersister PageStatePersister
{
get
{
if (_PageStatePersister == null)
{
CiroLightLibrary.BLL.ViewState myViewState = new ViewState();
if (Request["__DATABASE_VIEWSTATE"] != null)
myViewState.GUID = Request["__DATABASE_VIEWSTATE"].ToString();
else
myViewState.GUID = Guid.NewGuid().ToString();
_PageStatePersister = new DatabasePageStatePersister(this, myViewState.GUID);
Literal l = new Literal();
l.Text = "<div><input type=\"hidden\" name=\"__DATABASE_VIEWSTATE\" value=\"" + myViewState.GUID + "\" /></div>";
this.Form.Controls.Add(l);
}
return _PageStatePersister;
}
}
}
And heres a Test page:
public partial class test : PageViewStateDatabaseStored
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
imgButton1.CommandArgument = "1";
btnButton1.CommandArgument = "1";
}
}
protected void imgButton_OnCommand(object sender, CommandEventArgs e)
{
Response.Write(e.CommandArgument.ToString());
}
protected void imgButton_OnClick(object sender, EventArgs e)
{
Response.Write("Click");
}
}
Asp.net Page
<form runat="server">
<asp:ImageButton ID="imgButton1" runat="server" OnCommand="imgButton_OnCommand" />
<asp:ImageButton ID="imgButton2" runat="server" OnClick="imgButton_OnClick" />
<asp:Button ID="btnButton1" runat="server" OnCommand="imgButton_OnCommand" />
<asp:Button ID="btnButton2" runat="server" OnClick="imgButton_OnClick" />
</form>
I tried to override LoadPageStateFromPersistenceMedium() and SavePageStateToPersistenceMedium() instead, and now my asp:ImageButtons also fires the events.
public class PageViewStateDatabaseStored : Page
{
protected override object LoadPageStateFromPersistenceMedium()
{
CiroLightLibrary.BLL.ViewState myViewState = new ViewState();
if (Request["__DATABASE_VIEWSTATE"] != null)
{
myViewState.GUID = Request["__DATABASE_VIEWSTATE"].ToString();
myViewState.Load();
}
LosFormatter myFormatter = new LosFormatter();
return myFormatter.Deserialize(myViewState.Value);
}
protected override void SavePageStateToPersistenceMedium(object viewState)
{
CiroLightLibrary.BLL.ViewState myViewState = new ViewState();
if (Request["__DATABASE_VIEWSTATE"] != null)
myViewState.GUID = Request["__DATABASE_VIEWSTATE"].ToString();
else
myViewState.GUID = Guid.NewGuid().ToString();
LosFormatter myFormatter = new LosFormatter();
StringWriter myStringWriter = new StringWriter();
myFormatter.Serialize(myStringWriter, viewState);
myViewState.Value = myStringWriter.ToString();
myViewState.Save();
ScriptManager.RegisterHiddenField(this, "__DATABASE_VIEWSTATE", myViewState.GUID);
}
}
I know this is an old post.... But the answer is simply that you're saving the viewstate not the controlstate.
public override void Load()
{
myViewState.Load();
var pair = (Pair)this.StateFormatter.Deserialize(myViewState.Value);
this.ViewState = pair.First;
this.ControlState = pair.Second;
}
public override void Save()
{
myViewState.Value = this.StateFormatter.Serialize(new Pair(this.ViewState, this.ControlState));
myViewState.Save();
}
Personally I'd inherit my PageStatePersister from HiddenFieldPageStatePersister then in my save method write the guid into the viewstate property and set the controlstate to null, then call the base Save method. In the Load, call the load the base.load then get the GUID from the viewstate property before pushing the db aquired values into the viewstate, controlstate properties. That way we're not modifying the control tree.... Like so:
public class MyPageStatePersister : System.Web.UI.HiddenFieldPageStatePersister
{
public MyPageStatePersister(Page page)
: base(page)
{
}
public override void Load()
{
base.Load();
this.CurrentKey = (Guid)this.ViewState;
var s = this.CurrentKey;
var state = SomeDAOManager.GetState(s);
if (state != null)
{
this.ViewState = state.First;
this.ControlState = state.Second;
}
else
{
this.ControlState = null;
this.ViewState = null;
}
}
public Guid CurrentKey {get;set;}
public override void Save()
{
if (CurrentKey == Guid.Empty)
{
this.CurrentKey = Guid.NewGuid();
}
Guid guid = CurrentKey;
SomeDAOManager.SaveState(guid, new Pair(this.ViewState, this.ControlState));
this.ViewState = guid;
this.ControlState = null;
base.Save();
}
}

How to write a custom templatefield-like DataControlField

I am using a GridView to display data where one of the data columns has type DateTimeOffset. In order to display dates & times in the user's timezone, I save the user's timezone preference to his or her profile (property value key "TimezoneOffset"), and need to access it when formatting dates & times.
If I were to use templatefield, then I would need to write:
<abbr class="datetimeoffset">
<%#
((DateTimeOffset)Eval("CreatedDate"))
.ToOffset(new TimeSpan(-((Int32)Profile.GetPropertyValue("TimezoneOffset"))
.ToRepresentativeInRange(-12, 24), 0, 0)).ToString("f") %>
</abbr>
which is too complicated and not reusable.
I tried adding a TimeSpan property to the code-behind (to at least move that out of the data binding expression), but apparently properties of the view's code-behind are inaccessible within <%# ... %>.
Therefore, I think that I need to write a custom DataControlField to format dates & times in the user's timezone.
I have started with:
public class DateTimeOffsetField : DataControlField
{
private TimeSpan userOffsetTimeSpan;
protected override DataControlField CreateField()
{
return new DateTimeOffsetField();
}
protected override void CopyProperties(DataControlField newField)
{
base.CopyProperties(newField);
((DateTimeOffsetField)newField).userOffsetTimeSpan = userOffsetTimeSpan;
}
public override bool Initialize(bool sortingEnabled, System.Web.UI.Control control)
{
bool ret = base.Initialize(sortingEnabled, control);
int timezoneOffset = ((Int32)HttpContext.Current.Profile.GetPropertyValue("TimezoneOffset")).ToRepresentativeInRange(-12, 24);
userOffsetTimeSpan = new TimeSpan(-timezoneOffset, 0, 0);
return ret;
}
}
But now I am stuck. How do I output the HTML <abbr class="datetimeoffset"><%# ((DateTimeOffset)Eval("CreatedDate")).ToOffset(userOffsetTimeSpan).ToString("f") %></abbr> for each cell?
EDIT: I have been reading an article titled Cutting Edge: Custom Data Control Fields. So far I have added:
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
base.InitializeCell(cell, cellType, rowState, rowIndex);
if (cellType == DataControlCellType.DataCell)
{
InitializeDataCell(cell, rowState, rowIndex);
}
}
protected virtual void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState, int rowIndex)
{
System.Web.UI.Control control = cell;
if (control != null && Visible)
{
control.DataBinding += new EventHandler(OnBindingField);
}
}
protected virtual void OnBindingField(object sender, EventArgs e)
{
var target = (System.Web.UI.Control)sender;
if (target is TableCell)
{
TableCell tc = (TableCell)target;
}
}
but whereas the article sets the Text property of the TableCell instance, I would like to render a partial view into the table cell. Is that possible?
I figured it out. Here is what I ended up with:
// DateTimeOffsetField.cs
public class DateTimeOffsetField : BoundField
{
private TimeSpan userOffsetTimeSpan;
protected override DataControlField CreateField()
{
return new DateTimeOffsetField();
}
protected override void CopyProperties(DataControlField newField)
{
base.CopyProperties(newField);
((DateTimeOffsetField)newField).userOffsetTimeSpan = userOffsetTimeSpan;
}
public override bool Initialize(bool sortingEnabled, System.Web.UI.Control control)
{
bool ret = base.Initialize(sortingEnabled, control);
int timezoneOffset = ((Int32)HttpContext.Current.Profile.GetPropertyValue("TimezoneOffset")).ToRepresentativeInRange(-12, 24);
userOffsetTimeSpan = new TimeSpan(-timezoneOffset, 0, 0);
return ret;
}
protected override void OnDataBindField(object sender, EventArgs e)
{
base.OnDataBindField(sender, e);
var target = (Control)sender;
if (target is TableCell)
{
var tc = (TableCell)target;
var dataItem = DataBinder.GetDataItem(target.NamingContainer);
var dateTimeOffset = (DateTimeOffset)DataBinder.GetPropertyValue(dataItem, DataField);
tc.Controls.Add(new TimeagoDateTimeOffset { DateTimeOffset = dateTimeOffset.ToOffset(userOffsetTimeSpan) });
}
}
}
TimeagoDateTimeOffset.cs:
[DefaultProperty("DateTimeOffset")]
[ToolboxData("<{0}:TimeagoDateTimeOffset runat=server></{0}:TimeagoDateTimeOffset>")]
public class TimeagoDateTimeOffset : WebControl
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public DateTimeOffset DateTimeOffset
{
get { return (DateTimeOffset)ViewState["DateTimeOffset"]; }
set { ViewState["DateTimeOffset"] = value; }
}
protected override void RenderContents(HtmlTextWriter writer)
{
writer.BeginRender();
writer.AddAttribute(HtmlTextWriterAttribute.Class, "timeago", false);
writer.AddAttribute(HtmlTextWriterAttribute.Title, DateTimeOffset.ToString("o"));
writer.RenderBeginTag("abbr");
writer.Write(DateTimeOffset.ToString("d"));
writer.RenderEndTag();
writer.EndRender();
}
}

Getting dynamically added child controls to display in the UI

I am trying to create a RadioButtonListWithOther class that extends the RadoButtonList but I can't get the "Other" textbox to render on the page. When I step through while debugging I can see the control in the parent control's Controls collectio but it still doesn't render. Any ideas what I am doing wrong here?
public class RadioButtonListWithOther : RadioButtonList
{
private TextBox _otherReason;
public RadioButtonListWithOther()
{
_otherReason = new TextBox();
_otherReason.TextMode = TextBoxMode.MultiLine;
_otherReason.Rows = 6;
_otherReason.Width = Unit.Pixel(300);
_otherReason.Visible = true;
}
protected override void CreateChildControls()
{
this.Controls.Add(_otherReason);
this.EnsureChildControls();
base.CreateChildControls();
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
_otherReason.Enabled = false;
if (OtherSelected())
{
_otherReason.Enabled = true;
}
base.OnSelectedIndexChanged(e);
}
public override string Text
{
get
{
if (OtherSelected())
{
return _otherReason.Text;
}
return base.Text;
}
set
{
base.Text = value;
}
}
public override bool Visible
{
get
{
return base.Visible;
}
set
{
//Push visibility changes down to the children controls
foreach (Control control in this.Controls)
{
control.Visible = value;
}
base.Visible = value;
}
}
private bool OtherSelected()
{
if (this.SelectedItem.Text == "Other")
{
return true;
}
return false;
}
}
Here is my code to add an instance of this control to the WebForm:
protected override void CreateChildControls()
{
var whyMentorOptions = new Dictionary<string, string>();
whyMentorOptions.Add("Option 1", "1");
whyMentorOptions.Add("Option 2", "2");
whyMentorOptions.Add("Option 3", "3");
whyMentorOptions.Add("Other", "Other");
mentorWhy = new RadioButtonListWithOther
{
DataSource = whyMentorOptions
};
this.mentorWhy.DataTextField = "Key";
this.mentorWhy.DataValueField = "Value";
this.mentorWhy.DataBind();
Form.Controls.Add(mentorWhy);
base.CreateChildControls();
}
The RadioButtonList class completely ignores its child controls when rendering (it's only interested in the contents of its Items collection).
You'll have to render the text box yourself:
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
_otherReason.RenderControl(writer);
}

Categories