Problem overriding render method to format literal content of nested tags in custom control

S

Stephen Miller

Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags. This works
fine, unless I add a RequiredFieldValidator control at which point my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render' method on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to replicate my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult" runat="server">lblResult</asp:label></P>
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting logic in the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a 'RequiredFieldValidator'. How can
I do this?

Regards,

Stephen
 
S

Stephen Miller

Peter,

Thanks for the link. I have enjoyed looking at your site, but its not
going to give me the answer I need. My control needs to be able to
render any nested literal content the developer throws at it, so I
have to resolve this problem with MS's validators.

I have designed my control in such a way that it only needs to
validate controls in the same naming container but as you noted, the
error message seems to indicate that the control thinks the textbox
and validator are in different containers.

What I find particularly puzzling is that if I simple override the
render method with 'base.Render(output)' the validator has not problem
locating the textbox in the container. The problem only seems to arise
when I attempt to encapsulate the controls in my own formatting with
'table.RenderControl(output)'.

Should I be looking at overriding the 'table.RenderControl' method to
make this work? If so what should I be doing?

I recently found another commercial product that is able to do what
I'm trying to achive
(http://www.infragistics.com/products/navigation.asp?sec=0&cat=3) so I
know it must be possible (... and I don't want to pay USD $500)

Thanks,

Stephen



Peter Blum said:
The ControlToValidate property only accepts the ID of another control in the
same naming container. I believe that this error is due to naming container
problems. While your code seems to enclose both the textbox and validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming container. The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Stephen Miller said:
Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags. This works
fine, unless I add a RequiredFieldValidator control at which point my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render' method on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to replicate my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult" runat="server">lblResult</asp:label></P>
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting logic in the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a 'RequiredFieldValidator'. How can
I do this?

Regards,

Stephen
 
S

Stephen Miller

Peter,

I was under the mistaken impression that I was already overriding
CreateChildControls to create the controls with the code:

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

If I leave this override out then the control renders fine (with the
RequiredFieldValidators) but fails to generate any server side events.
Moving this code block to OnPreRender (with base.OnPreRend) after
adding the controls doesn't seem to make any difference.

I have also tried moving the code in the overridden Render method to
the overridden RenderContents method. This doesn't appear to have made
any difference.

Have I missed the point? Could I beg you for some sample code?

Thanks,

Stephen

Peter Blum said:
I think I see the problem. You are creating your control objects in the
Render method. Leave the Render method alone. Generally, only use it if you
wanted to output explicit HTML (although there are some cases where you can
do what you did.) Instead, create the controls in either the
CreateChildControls() or OnPreRender method.

Here's the actual problem you are having. Validators must have their
OnPreRender method run. Because your validators aren't in this control's
Controls property, they don't get OnPreRender run. So move your code to
OnPreRender and call base.OnPreRender() after those controls are added to
the Controls property.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Stephen Miller said:
Peter,

Thanks for the link. I have enjoyed looking at your site, but its not
going to give me the answer I need. My control needs to be able to
render any nested literal content the developer throws at it, so I
have to resolve this problem with MS's validators.

I have designed my control in such a way that it only needs to
validate controls in the same naming container but as you noted, the
error message seems to indicate that the control thinks the textbox
and validator are in different containers.

What I find particularly puzzling is that if I simple override the
render method with 'base.Render(output)' the validator has not problem
locating the textbox in the container. The problem only seems to arise
when I attempt to encapsulate the controls in my own formatting with
'table.RenderControl(output)'.

Should I be looking at overriding the 'table.RenderControl' method to
make this work? If so what should I be doing?

I recently found another commercial product that is able to do what
I'm trying to achive
(http://www.infragistics.com/products/navigation.asp?sec=0&cat=3) so I
know it must be possible (... and I don't want to pay USD $500)

Thanks,

Stephen



"Peter Blum" <[email protected]> wrote in message
The ControlToValidate property only accepts the ID of another control in the
same naming container. I believe that this error is due to naming container
problems. While your code seems to enclose both the textbox and validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming container. The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags. This works
fine, unless I add a RequiredFieldValidator control at which point my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render' method on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to replicate my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[

DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult" runat="server">lblResult</asp:label></P>
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting logic in the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a 'RequiredFieldValidator'. How can
I do this?

Regards,

Stephen
 
S

Stephen Miller

Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen


Peter Blum said:
I strongly encourage you to step back and learn the right way to build
custom controls. Otherwise, you will be developing code that may require
plenty more debugging and maintenance because it doesn't confirm to how
Microsoft designed web controls. I recommend the book "Developing ASP.NET
Server Controls and Components" from Microsoft Press.

In your code, I do not understand why you have the _NestedChildren property.
The webcontrol framework automatically supports child controls using the
ParseChildrenAttribute. (See the .net docs on this attribute.) I do
understand what you are doing in CreateChildControls and that looks OK.

Here are my recommendations.
- Move the code you have creating the table and its contents into
CreateChildControls without removing the existing code.
- Any "output.Write()" calls can become LiteralControl objects. (new
LiteralControl("<br>"))
- Add objects to the Controls collection on your control.

Now your validators will be created before OnPreRender occurs. That allows
the webcontrol system that Microsoft designed to call each of your
validators own OnPreRender method properly, setting up many things that are
required for validation.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)
Stephen Miller said:
Peter,

I was under the mistaken impression that I was already overriding
CreateChildControls to create the controls with the code:

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

If I leave this override out then the control renders fine (with the
RequiredFieldValidators) but fails to generate any server side events.
Moving this code block to OnPreRender (with base.OnPreRend) after
adding the controls doesn't seem to make any difference.

I have also tried moving the code in the overridden Render method to
the overridden RenderContents method. This doesn't appear to have made
any difference.

Have I missed the point? Could I beg you for some sample code?

Thanks,

Stephen

"Peter Blum" <[email protected]> wrote in message
control in
the
same naming container. I believe that this error is due to naming container
problems. While your code seems to enclose both the textbox and validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming container. The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags. This works
fine, unless I add a RequiredFieldValidator control at which point my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render' method on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to replicate my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[


DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult"
runat="server">lblResult said:
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting logic in the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a 'RequiredFieldValidator'. How can
I do this?

Regards,

Stephen
 
A

Alessandro Zifiglio

have your control implement INamingContainer. This will generate Unique Ids
Stephen Miller said:
Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen


"Peter Blum" <[email protected]> wrote in message
I strongly encourage you to step back and learn the right way to build
custom controls. Otherwise, you will be developing code that may require
plenty more debugging and maintenance because it doesn't confirm to how
Microsoft designed web controls. I recommend the book "Developing ASP.NET
Server Controls and Components" from Microsoft Press.

In your code, I do not understand why you have the _NestedChildren property.
The webcontrol framework automatically supports child controls using the
ParseChildrenAttribute. (See the .net docs on this attribute.) I do
understand what you are doing in CreateChildControls and that looks OK.

Here are my recommendations.
- Move the code you have creating the table and its contents into
CreateChildControls without removing the existing code.
- Any "output.Write()" calls can become LiteralControl objects. (new
LiteralControl("<br>"))
- Add objects to the Controls collection on your control.

Now your validators will be created before OnPreRender occurs. That allows
the webcontrol system that Microsoft designed to call each of your
validators own OnPreRender method properly, setting up many things that are
required for validation.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)
Stephen Miller said:
Peter,

I was under the mistaken impression that I was already overriding
CreateChildControls to create the controls with the code:

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

If I leave this override out then the control renders fine (with the
RequiredFieldValidators) but fails to generate any server side events.
Moving this code block to OnPreRender (with base.OnPreRend) after
adding the controls doesn't seem to make any difference.

I have also tried moving the code in the overridden Render method to
the overridden RenderContents method. This doesn't appear to have made
any difference.

Have I missed the point? Could I beg you for some sample code?

Thanks,

Stephen

"Peter Blum" <[email protected]> wrote in message
I think I see the problem. You are creating your control objects in the
Render method. Leave the Render method alone. Generally, only use it
if
you
wanted to output explicit HTML (although there are some cases where
you
can
do what you did.) Instead, create the controls in either the
CreateChildControls() or OnPreRender method.

Here's the actual problem you are having. Validators must have their
OnPreRender method run. Because your validators aren't in this control's
Controls property, they don't get OnPreRender run. So move your code to
OnPreRender and call base.OnPreRender() after those controls are
added
to
the Controls property.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Peter,

Thanks for the link. I have enjoyed looking at your site, but its not
going to give me the answer I need. My control needs to be able to
render any nested literal content the developer throws at it, so I
have to resolve this problem with MS's validators.

I have designed my control in such a way that it only needs to
validate controls in the same naming container but as you noted, the
error message seems to indicate that the control thinks the textbox
and validator are in different containers.

What I find particularly puzzling is that if I simple override the
render method with 'base.Render(output)' the validator has not problem
locating the textbox in the container. The problem only seems to arise
when I attempt to encapsulate the controls in my own formatting with
'table.RenderControl(output)'.

Should I be looking at overriding the 'table.RenderControl' method to
make this work? If so what should I be doing?

I recently found another commercial product that is able to do what
I'm trying to achive
(http://www.infragistics.com/products/navigation.asp?sec=0&cat=3) so I
know it must be possible (... and I don't want to pay USD $500)

Thanks,

Stephen



"Peter Blum" <[email protected]> wrote in message
The ControlToValidate property only accepts the ID of another
control in
the
same naming container. I believe that this error is due to
naming
container
problems. While your code seems to enclose both the textbox and validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming
container.
The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags. This works
fine, unless I add a RequiredFieldValidator control at which
point
my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render'
method
on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to
replicate
my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult"
runat="server">lblResult said:
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting logic
in
the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a 'RequiredFieldValidator'.
How
can
I do this?

Regards,

Stephen
 
A

Alessandro Zifiglio

in case it is not clear, i'm referring to the template you are using, which
inherits control. When you develop templated controls, you should implement
this interface InameingContainer to avoid naming conflicts on a page. . Your
main class nested is un-necessarily implementing this interface as you
already have clientID which is is unique, and you can name your table :
clientID + "_table";

Alessandro Zifiglio said:
have your control implement INamingContainer. This will generate Unique Ids
Stephen Miller said:
Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen


"Peter Blum" <[email protected]> wrote in message
I strongly encourage you to step back and learn the right way to build
custom controls. Otherwise, you will be developing code that may require
plenty more debugging and maintenance because it doesn't confirm to how
Microsoft designed web controls. I recommend the book "Developing ASP.NET
Server Controls and Components" from Microsoft Press.

In your code, I do not understand why you have the _NestedChildren property.
The webcontrol framework automatically supports child controls using the
ParseChildrenAttribute. (See the .net docs on this attribute.) I do
understand what you are doing in CreateChildControls and that looks OK.

Here are my recommendations.
- Move the code you have creating the table and its contents into
CreateChildControls without removing the existing code.
- Any "output.Write()" calls can become LiteralControl objects. (new
LiteralControl("<br>"))
- Add objects to the Controls collection on your control.

Now your validators will be created before OnPreRender occurs. That allows
the webcontrol system that Microsoft designed to call each of your
validators own OnPreRender method properly, setting up many things
that
in
it
where
code
its
method
(http://www.infragistics.com/products/navigation.asp?sec=0&cat=3)
so I
know it must be possible (... and I don't want to pay USD $500)

Thanks,

Stephen



The ControlToValidate property only accepts the ID of another
control in
the
same naming container. I believe that this error is due to naming
container
problems. While your code seems to enclose both the textbox and
validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a
commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming container.
The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Gurus,

I have a custom web control that in turn has nested child
controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags. This
works
fine, unless I add a RequiredFieldValidator control at which point
my
aspx page fails at
'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild'
controls
with a simple 'Caption' property. I override the 'Render' method
on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to replicate
my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[



DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult"
runat="server">lblResult</asp:label></P>
</form>

If I remove 'RequiredFieldValidator' my control render as
expected.

I have noticed, that If I replace the entire formatting
logic
'RequiredFieldValidator'.
 
S

Stephen Miller

Alessandro,

That's the missing piece of the puzzle!

I can see that I am now uncessarily implementing InamingContainer
twice, however I found that if I gave the table a unique name (ie.
table.ID = "_" + this.ID; ) and removed InamingContainer from the main
class ('Nested'), then the control correctly renders any nested server
controls placed within the 'NestedChild' tags (including
RequiredFieldValidator's), but the command button was unable to
generate a server-side onClick event.

Having two implementations of InamingContainer does produce
complicated naming of child controls, when rendered (ie cmdTest
becomes Nested1:_ctl2:cmdTest). Is there a better way? Should I now be
moving my implementation of IPostBackDataHandler to the 'NestedChild'
class'?

Regards,

Stephen


Alessandro Zifiglio said:
in case it is not clear, i'm referring to the template you are using, which
inherits control. When you develop templated controls, you should implement
this interface InameingContainer to avoid naming conflicts on a page. . Your
main class nested is un-necessarily implementing this interface as you
already have clientID which is is unique, and you can name your table :
clientID + "_table";

Alessandro Zifiglio said:
have your control implement INamingContainer. This will generate Unique Ids
Stephen Miller said:
Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen


"Peter Blum" <[email protected]> wrote in message
I strongly encourage you to step back and learn the right way to build
custom controls. Otherwise, you will be developing code that may require
plenty more debugging and maintenance because it doesn't confirm to how
Microsoft designed web controls. I recommend the book "Developing ASP.NET
Server Controls and Components" from Microsoft Press.

In your code, I do not understand why you have the _NestedChildren property.
The webcontrol framework automatically supports child controls using the
ParseChildrenAttribute. (See the .net docs on this attribute.) I do
understand what you are doing in CreateChildControls and that looks OK.

Here are my recommendations.
- Move the code you have creating the table and its contents into
CreateChildControls without removing the existing code.
- Any "output.Write()" calls can become LiteralControl objects. (new
LiteralControl("<br>"))
- Add objects to the Controls collection on your control.

Now your validators will be created before OnPreRender occurs. That allows
the webcontrol system that Microsoft designed to call each of your
validators own OnPreRender method properly, setting up many things
that
are
required for validation.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)
Peter,

I was under the mistaken impression that I was already overriding
CreateChildControls to create the controls with the code:

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

If I leave this override out then the control renders fine (with the
RequiredFieldValidators) but fails to generate any server side events.
Moving this code block to OnPreRender (with base.OnPreRend) after
adding the controls doesn't seem to make any difference.

I have also tried moving the code in the overridden Render method to
the overridden RenderContents method. This doesn't appear to have made
any difference.

Have I missed the point? Could I beg you for some sample code?

Thanks,

Stephen

"Peter Blum" <[email protected]> wrote in message
I think I see the problem. You are creating your control objects
in
the
Render method. Leave the Render method alone. Generally, only use
it
if
you
wanted to output explicit HTML (although there are some cases
where
you
can
do what you did.) Instead, create the controls in either the
CreateChildControls() or OnPreRender method.

Here's the actual problem you are having. Validators must have their
OnPreRender method run. Because your validators aren't in this control's
Controls property, they don't get OnPreRender run. So move your
code
to
OnPreRender and call base.OnPreRender() after those controls are
added
to
the Controls property.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Peter,

Thanks for the link. I have enjoyed looking at your site, but
its
not
going to give me the answer I need. My control needs to be able to
render any nested literal content the developer throws at it, so I
have to resolve this problem with MS's validators.

I have designed my control in such a way that it only needs to
validate controls in the same naming container but as you noted, the
error message seems to indicate that the control thinks the textbox
and validator are in different containers.

What I find particularly puzzling is that if I simple override the
render method with 'base.Render(output)' the validator has not problem
locating the textbox in the container. The problem only seems to arise
when I attempt to encapsulate the controls in my own formatting with
'table.RenderControl(output)'.

Should I be looking at overriding the 'table.RenderControl'
method
to
make this work? If so what should I be doing?

I recently found another commercial product that is able to do what
I'm trying to achive
(http://www.infragistics.com/products/navigation.asp?sec=0&cat=3)
so I
know it must be possible (... and I don't want to pay USD $500)

Thanks,

Stephen



"Peter Blum" <[email protected]> wrote in message
The ControlToValidate property only accepts the ID of another
control in
the
same naming container. I believe that this error is due to
naming
container
problems. While your code seems to enclose both the textbox
and
validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming
container.
The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags.
This
works
fine, unless I add a RequiredFieldValidator control at which
point
my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render'
method
on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to
replicate
my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[



DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult"
runat="server">lblResult said:
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting
logic
in
the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a
'RequiredFieldValidator'.
How
can
I do this?

Regards,

Stephen
 
S

Stephen Miller

Guys,

.... Two steps forward, one step back

I've found another peculiarity regarding event handling. Extending the
simplified version of the code posted previously, my control also
implements IpostBackEventHandler and raises a default event
'SelectedIndexChanged' for the onClick event for each nested child's
table cell. For example:

....
td.Attributes.Add("onclick", "javascript:" +
Page.GetPostBackEventReference(this,
_NestedChildren.IndexOf(NestedChild).ToString()))
....

I have implemented the RaisePostBackEvent to pass the event argument
to a local variable.

void IPostBackEventHandler.RaisePostBackEvent(String strEventArgument)
{

if (strEventArgument == null)
return;

_selectedIndex = Int32.Parse(strEventArgument);

}

This enables a user to select a particular NestedChild, with further
logic on postback.

Using page tracing and liberal use of the 'Page.Trace.Write' method, I
have observed some strange postback handling.

If a selected NestedChild contains just simple HTML markup, then the
processing order on onClick is:

LoadViewState
ProcessPostData
`-> IPostBackDataHandler.LoadPostData
ChangedEvents
`-> IPostBackDataHandler.RaisePostDataChangedEvent
PostBackEvent
`-> IPostBackEventHandler.RaisePostBackEvent
PreRender
`-> CreateChildControls
SaveViewState
Render

If however, I select a NestedChild that contains server controls, then
the processing order becomes:

LoadViewState
ProcessPostData
`-> CreateChildControls
`-> IPostBackDataHandler.LoadPostData
ChangedEvents
`-> IPostBackDataHandler.RaisePostDataChangedEvent
PostBackEvent
`-> IPostBackEventHandler.RaisePostBackEvent
PreRender
SaveViewState
Render

I'm expecting CreateChildControls to be called in the PreRender phase
(after RaisePostBackEvent, when the _selectedIndex becomes known). Why
is CreateChildControls not being called in PreRender, when the
NestedChild that contains server controls? How can I raise the
PostBackEvent (and the OnSelectedIndexChanged method), before the
CreateChildControls method is called?

Regards,

Stephen

Alessandro Zifiglio said:
in case it is not clear, i'm referring to the template you are using, which
inherits control. When you develop templated controls, you should implement
this interface InameingContainer to avoid naming conflicts on a page. . Your
main class nested is un-necessarily implementing this interface as you
already have clientID which is is unique, and you can name your table :
clientID + "_table";

Alessandro Zifiglio said:
have your control implement INamingContainer. This will generate Unique Ids
Stephen Miller said:
Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen


"Peter Blum" <[email protected]> wrote in message
I strongly encourage you to step back and learn the right way to build
custom controls. Otherwise, you will be developing code that may require
plenty more debugging and maintenance because it doesn't confirm to how
Microsoft designed web controls. I recommend the book "Developing ASP.NET
Server Controls and Components" from Microsoft Press.

In your code, I do not understand why you have the _NestedChildren property.
The webcontrol framework automatically supports child controls using the
ParseChildrenAttribute. (See the .net docs on this attribute.) I do
understand what you are doing in CreateChildControls and that looks OK.

Here are my recommendations.
- Move the code you have creating the table and its contents into
CreateChildControls without removing the existing code.
- Any "output.Write()" calls can become LiteralControl objects. (new
LiteralControl("<br>"))
- Add objects to the Controls collection on your control.

Now your validators will be created before OnPreRender occurs. That allows
the webcontrol system that Microsoft designed to call each of your
validators own OnPreRender method properly, setting up many things
that
are
required for validation.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)
Peter,

I was under the mistaken impression that I was already overriding
CreateChildControls to create the controls with the code:

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

If I leave this override out then the control renders fine (with the
RequiredFieldValidators) but fails to generate any server side events.
Moving this code block to OnPreRender (with base.OnPreRend) after
adding the controls doesn't seem to make any difference.

I have also tried moving the code in the overridden Render method to
the overridden RenderContents method. This doesn't appear to have made
any difference.

Have I missed the point? Could I beg you for some sample code?

Thanks,

Stephen

"Peter Blum" <[email protected]> wrote in message
I think I see the problem. You are creating your control objects
in
the
Render method. Leave the Render method alone. Generally, only use
it
if
you
wanted to output explicit HTML (although there are some cases
where
you
can
do what you did.) Instead, create the controls in either the
CreateChildControls() or OnPreRender method.

Here's the actual problem you are having. Validators must have their
OnPreRender method run. Because your validators aren't in this control's
Controls property, they don't get OnPreRender run. So move your
code
to
OnPreRender and call base.OnPreRender() after those controls are
added
to
the Controls property.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Peter,

Thanks for the link. I have enjoyed looking at your site, but
its
not
going to give me the answer I need. My control needs to be able to
render any nested literal content the developer throws at it, so I
have to resolve this problem with MS's validators.

I have designed my control in such a way that it only needs to
validate controls in the same naming container but as you noted, the
error message seems to indicate that the control thinks the textbox
and validator are in different containers.

What I find particularly puzzling is that if I simple override the
render method with 'base.Render(output)' the validator has not problem
locating the textbox in the container. The problem only seems to arise
when I attempt to encapsulate the controls in my own formatting with
'table.RenderControl(output)'.

Should I be looking at overriding the 'table.RenderControl'
method
to
make this work? If so what should I be doing?

I recently found another commercial product that is able to do what
I'm trying to achive
(http://www.infragistics.com/products/navigation.asp?sec=0&cat=3)
so I
know it must be possible (... and I don't want to pay USD $500)

Thanks,

Stephen



"Peter Blum" <[email protected]> wrote in message
The ControlToValidate property only accepts the ID of another
control in
the
same naming container. I believe that this error is due to
naming
container
problems. While your code seems to enclose both the textbox
and
validator
within the same naming container, the error message says otherwise.

FYI: If the naming container is indeed the problem, I have a commercial
product that replaces Microsoft's validators to overcome its many
limitations. My validators support controls in any naming
container.
The
product is "Professional Validation And More". Details are at
http://www.peterblum.com/vam/home.aspx.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Gurus,

I have a custom web control that in turn has nested child controls. I
want to be able to encapsulated and render any literal HTML and or
server controls placed between the child control's tags.
This
works
fine, unless I add a RequiredFieldValidator control at which
point
my
aspx page fails at 'WebControls.BaseValidator.CheckControlValidationProperty'
with the error message 'Unable to find control id 'TextBox1'
referenced by the 'ControlToValidate' property of
'RequiredFieldValidator1'.

My control 'Nested' contains a collection of 'NestedChild' controls
with a simple 'Caption' property. I override the 'Render'
method
on
the 'Nested' control and iterate though each NestedChild' in the
collection, outputting the child's literal content to a formatted
table.

The full code for my control is (please cut-n-paste to
replicate
my
problem):

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.HtmlControls;

namespace Custom.Web.UI {

[ToolboxData("<{0}:Nested runat=server></{0}:Nested2>")]
[ParseChildren(true, "NestedChild"), PersistChildren(false)]
public class Nested : WebControl, INamingContainer,
IPostBackDataHandler {

private NestedChildCollection _NestedChildren = new
NestedChildCollection();

#region Properties
[



DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerDefaultProperty),
Description("A collection of nested children"),
]
public NestedChildCollection NestedChild {
get{ return _NestedChildren; }
}
#endregion

#region Overrides

protected override void CreateChildControls() {
this.Controls.Clear();
foreach (NestedChild child in _NestedChildren) {
this.Controls.Add(child);
}
}

protected override void Render(HtmlTextWriter output) {
output.Write("<b>before</b><br /><hr><br />");
//base.Render(output);
foreach(NestedChild child in _NestedChildren) {
output.Write("* " + child.Caption + "<BR>");

// Display the literal contents of NestedChild
// in a single table cell
HtmlTableCell td = new HtmlTableCell();
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

/* FAILS NEXT LINE
Error: "Unable to find control id 'txtTest1' referenced
by the 'ControlToValidate' property of 'valTest1'"
/*
table.RenderControl(output);
}
output.Write("<br /><hr><br /><b>after</b>");
}
#endregion

#region Implements IPostBackDataHandler (postback only)
bool IPostBackDataHandler.LoadPostData(string strPostDataKey,
NameValueCollection postDataCollection) {
return true;
}
void IPostBackDataHandler.RaisePostDataChangedEvent() {}
#endregion
}

[ToolboxItem(false), DefaultProperty("Caption")]
public class NestedChild : Control{
private String m_strCaption;
public String Caption {
get { return m_strCaption; }
set { m_strCaption = value; }
}
}

public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}
}

When deployed the aspx page looks like:

<form id="Form1" method="post" runat="server">
<P>Nested Control</P>
<P>
<cc1:Nested id="Nested1" runat="server">
<cc1:NestedChild Caption="NestedChild">
<asp:Button id="cmdTest" runat="server" Text="Generate
Event"></asp:Button>
<asp:TextBox id="txtTest" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator id="valTest" runat="server"
ControlToValidate="txtTest"
ErrorMessage="This field is
Required.">*</asp:RequiredFieldValidator>
</cc1:NestedChild>
</cc1:Nested2></P>
<P>&nbsp;</P>
<P>
<asp:label id="lblResult"
runat="server">lblResult said:
</form>

If I remove 'RequiredFieldValidator' my control render as expected.

I have noticed, that If I replace the entire formatting
logic
in
the
'Render' method with a call to 'base.Render(output);', the
RequiredFieldValidator works as expected.

I think this is quite a technical problem and I assume that
'table.RenderControl(output)' is not preforming the necessary
server-side pre-validation to pass a
'RequiredFieldValidator'.
How
can
I do this?

Regards,

Stephen
 
A

Alessandro Zifiglio

Now that the naming issue has been resolved by PeterBlum --your second
problem is the events being fired in your nestedchild controls. For any
events that fire in your template(nestedchild) you need to bubble them up to
the container(nested), you can then either take action in nested if this is
the last destination or bubble them futher up to the page, that is the
container of nested.

to handle or to raise the bubbled event, you must override the OnBubbleEvent
method for your control.

Now always building up on the same code you posted --the first one that is,
you have a button in your template --the clickevent for the button will fire
in your template --override the OnBubbleEvent method there and have it fire
in the container "nested" --in nested override the OnBUbbleEvent again and
send it up to its container(the page where the control sits)
You do not need to implement the IPostBackEventHandler for this. There is a
very nice example on msdn which goes into all the details. However its very
vast so i'm coping out the steps you need to take. For more look at the
example to which i'm including the link after the code below :
//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {

private TemplatedListItem item;
private object commandSource;

public TemplatedListCommandEventArgs(TemplatedListItem item, object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}

public TemplatedListItem Item {
get {
return item;
}
}

public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)




//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish----------------
--------------------------------------

now in your page where the control sits you have the event handler use it,
all events fired in your templates can be captured here --you now have one
unique place to search for events as it is with templated controls, you can
have many controls in there that fire events, and you do not want an event
handler for every control in there, instead you want ONE handler that will
handle all --the trick it is to use the commandName for your buttons and
then look for this command name here --that way you can differentiate
between buttons and take action accordingly. So on the button that you have
used in your template pass use the CommandName property to like say if you
set the CommandName="save" then you can check if the save button was the one
clicked, see code below :

protected void MyList_ItemCreated(object sender, TemplatedListItemEventArgs
e) {

if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}

}

This is the templated databound sample, surely you will learn a lot besides
the event bubbling ;P
Ok --please look at the sample code on MSDN.

If you have the docs then the link is :
ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpcontemplateddataboundcontrolsampl
e.htm

http://msdn.microsoft.com/library/d...html/cpcontemplateddataboundcontrolsample.asp

Not very hard to follow once you get a hang of it ;)





Peter Blum said:
Right now you're dealing with the ID property, as the error message tells
you.

If you do not assign an ID property, ASP.NET will automatically assign one
for your. However, if you need to refer to that ID in some other control,
like in Validator.ControlToValidator, that doesn't help.

I don't see your code that creates the validators and textboxes. So I don't
know how you are assigning Ids there. But I can see how you are assigning
the ID to the table. Perhaps we can learn from that:
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;


Don't assign an ID to either UniqueID or ClientID properties. The ID should
be a unique name within the naming container. ASP.NET will convert it into a
unique ID in ClientID and UniqueID. Additionally, UniqueID introduces a
format that doesn't work well in the client ID side attribute. (When you see
the HTML <input type='text' id=[from ClientID] name=[from UniqueID] />)

Here's what I like to do for IDs of child controls within a custom control.
Use the ID of custom control + "_" + some name.
For example:
HtmlTable table = new HtmlTable();
table.ID = this.ID + "_Table";

Because your custom control's ID will already be unique within the naming
container, so will its child controls.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Stephen Miller said:
Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen
 
A

Alessandro Zifiglio

Stephen, when I made that last post, I was in a hurry and missed out a few
things. I have given the code another look and here are some corrections.
However note that I have not taken the time to test this with your code, but
that i have just copied and pasted the essential parts that you need to
implement, and I have tried to adapt it to your code.

Also note that the example on MSDN if you tried to compile it along with its
designer class --you will end up with errors, this is because there are
numerous bugs in the code and i had to make many corrections before i was
able to run that code myself --however it demonstrates how to code and puts
you in the right direction on many techniques like with using
templates --databinding --associating a designer to your control, event
bubbling etc

Here is a much better step by step --the one i did last I missed out a few
things like declaring the static event EventItemCommand as object and i
removed some un-necessary code --Still did not test --the rest is up to you
to get this working ;)

//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {
private NestedChildCollection item;
private object commandSource;

public TemplatedListCommandEventArgs(NestedChildCollection item,
object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}


public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first declare the static event variable
EventItemCommand as object
//Next Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)

private static readonly object EventItemCommand = new object();



//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish----------------
--------------------------------------


protected void MyList_ItemCreated(object sender,
TemplatedListCommandEventArgs e) {
if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}

}


Alessandro Zifiglio said:
Now that the naming issue has been resolved by PeterBlum --your second
problem is the events being fired in your nestedchild controls. For any
events that fire in your template(nestedchild) you need to bubble them up to
the container(nested), you can then either take action in nested if this is
the last destination or bubble them futher up to the page, that is the
container of nested.

to handle or to raise the bubbled event, you must override the OnBubbleEvent
method for your control.

Now always building up on the same code you posted --the first one that is,
you have a button in your template --the clickevent for the button will fire
in your template --override the OnBubbleEvent method there and have it fire
in the container "nested" --in nested override the OnBUbbleEvent again and
send it up to its container(the page where the control sits)
You do not need to implement the IPostBackEventHandler for this. There is a
very nice example on msdn which goes into all the details. However its very
vast so i'm coping out the steps you need to take. For more look at the
example to which i'm including the link after the code below :
//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {

private TemplatedListItem item;
private object commandSource;

public TemplatedListCommandEventArgs(TemplatedListItem item, object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}

public TemplatedListItem Item {
get {
return item;
}
}

public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)




//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish-------------- --
--------------------------------------

now in your page where the control sits you have the event handler use it,
all events fired in your templates can be captured here --you now have one
unique place to search for events as it is with templated controls, you can
have many controls in there that fire events, and you do not want an event
handler for every control in there, instead you want ONE handler that will
handle all --the trick it is to use the commandName for your buttons and
then look for this command name here --that way you can differentiate
between buttons and take action accordingly. So on the button that you have
used in your template pass use the CommandName property to like say if you
set the CommandName="save" then you can check if the save button was the one
clicked, see code below :

protected void MyList_ItemCreated(object sender, TemplatedListItemEventArgs
e) {

if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}

}

This is the templated databound sample, surely you will learn a lot besides
the event bubbling ;P
Ok --please look at the sample code on MSDN.

If you have the docs then the link is :
ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpcontemplateddataboundcontrolsampl
http://msdn.microsoft.com/library/d...html/cpcontemplateddataboundcontrolsample.asp

Not very hard to follow once you get a hang of it ;)





Peter Blum said:
Right now you're dealing with the ID property, as the error message tells
you.

If you do not assign an ID property, ASP.NET will automatically assign one
for your. However, if you need to refer to that ID in some other control,
like in Validator.ControlToValidator, that doesn't help.

I don't see your code that creates the validators and textboxes. So I don't
know how you are assigning Ids there. But I can see how you are assigning
the ID to the table. Perhaps we can learn from that:
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;


Don't assign an ID to either UniqueID or ClientID properties. The ID should
be a unique name within the naming container. ASP.NET will convert it
into
a
unique ID in ClientID and UniqueID. Additionally, UniqueID introduces a
format that doesn't work well in the client ID side attribute. (When you see
the HTML <input type='text' id=[from ClientID] name=[from UniqueID] />)

Here's what I like to do for IDs of child controls within a custom control.
Use the ID of custom control + "_" + some name.
For example:
HtmlTable table = new HtmlTable();
table.ID = this.ID + "_Table";

Because your custom control's ID will already be unique within the naming
container, so will its child controls.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Stephen Miller said:
Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen
 
S

Stephen Miller

Aleddandro,

I can't override the OnBubbleEvent method in NestedChildCollection
because it inherits from CollectionBase. However, following your logic
and the MSDN sample, I have gone ahead and overridden the
OnBubbleEvent in the template NestedChild, which inherits from Control
as well as the container, Nested. I've also added a new sealed class
NestedChildCommandEventArgs inheriting from CommandEventArgs and it's
deligate NestedChildCommandEventHandler.

I've added page tracing to the OnBubbleEvent method in the Nested and
NestedChild objects, however this doesn't appear to be called. Is
event bubbling useful only when I know in advance what server controls
are being nested in my control? The primary goal of my control is to
allow the developer to place any literal or control within the
NestedChild object and have them encapsulated within the Nested
object's formatting (in this case a table).

To demonstrate, I have put a sample (which hopefully will make clear
what I'm trying to achieve) at www.3la.com .au/tabs.aspx.

If my nested container contains a Button control or a RadioButtonList
or a DataGrid then, CreateChildControls is processed in the PreRender
phase as expected.

However, If my nested container contains a Button control, TextBox and
its associated RequiredFieldValidator then CreateChildControls is
called in the ProcessPostData phase before the SelectedIndexChanged
event is handled in PostBackEvent.

As such, it appears that under some circumstances CreateChildControls
is called in the ProcessPostData phase, rather then in the PreRender
phase of the control life cycle.

The MSDN guide to the control life cycle
(ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpconcontrolexecutionlifecycle.htm)
does not list the CreateChildControls method "because it is called
whenever the ASP.NET page framework needs to create the controls tree
and this method call is not limited to a specific phase in a control's
lifecycle. For example, CreateChildControls can be invoked when
loading a page, during data binding, or during rendering".

Of course this is very unhelpful if CreateChildControls depends on a
PostBackEvent.

I have found another reference to this problem (without solution) at
http://support.softartisans.com/kbview.aspx?ID=671 (see under the
apply named section, 'take heed')

So how do I control when CreateChildControls is called?

I had thought that I could call CreateChildControls (twice if
necessary) from onPreRender (after we have handled
RaisePostBackEvent), but this results in "Multiple controls with the
same ID" error. I don't understand this as CreateChildControls
includes a Controls.Clear() statement, which I assumed would reset the
control's naming hierarchy).

I then tried moving CreateChildControls to
IPostBackEventHandler.RaisePostBackEvent and bizarrely this fixed the
problem with CreateChildControls being called once, in the
PostBackEvent phase. Again I don't understand why this actually works,
given that the ProcessPostData phase (where CreateChildControls was
being called during some PostBack events) happens before the
PostBackEvent phase (Note the sample referred to above doesn't have
this workaround).

Moving right along, I've now encountered a problem with a DataGrid
nested in my template container. When I click away, I'm now getting
the error:

"Failed to load viewstate. The control tree into which viewstate is
being loaded must match the control tree that was used to save
viewstate during the previous request. For example, when adding
controls dynamically, the controls added during a post-back must match
the type and position of the controls added during the initial
request."

I'll start on this problem tomorrow ;)

Stephen


Alessandro Zifiglio said:
Stephen, when I made that last post, I was in a hurry and missed out a few
things. I have given the code another look and here are some corrections.
However note that I have not taken the time to test this with your code, but
that i have just copied and pasted the essential parts that you need to
implement, and I have tried to adapt it to your code.

Also note that the example on MSDN if you tried to compile it along with its
designer class --you will end up with errors, this is because there are
numerous bugs in the code and i had to make many corrections before i was
able to run that code myself --however it demonstrates how to code and puts
you in the right direction on many techniques like with using
templates --databinding --associating a designer to your control, event
bubbling etc

Here is a much better step by step --the one i did last I missed out a few
things like declaring the static event EventItemCommand as object and i
removed some un-necessary code --Still did not test --the rest is up to you
to get this working ;)

//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {
private NestedChildCollection item;
private object commandSource;

public TemplatedListCommandEventArgs(NestedChildCollection item,
object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}


public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first declare the static event variable
EventItemCommand as object
//Next Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)

private static readonly object EventItemCommand = new object();



//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish----------------
--------------------------------------


protected void MyList_ItemCreated(object sender,
TemplatedListCommandEventArgs e) {
if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}

}


Alessandro Zifiglio said:
Now that the naming issue has been resolved by PeterBlum --your second
problem is the events being fired in your nestedchild controls. For any
events that fire in your template(nestedchild) you need to bubble them up to
the container(nested), you can then either take action in nested if this is
the last destination or bubble them futher up to the page, that is the
container of nested.

to handle or to raise the bubbled event, you must override the OnBubbleEvent
method for your control.

Now always building up on the same code you posted --the first one that is,
you have a button in your template --the clickevent for the button will fire
in your template --override the OnBubbleEvent method there and have it fire
in the container "nested" --in nested override the OnBUbbleEvent again and
send it up to its container(the page where the control sits)
You do not need to implement the IPostBackEventHandler for this. There is a
very nice example on msdn which goes into all the details. However its very
vast so i'm coping out the steps you need to take. For more look at the
example to which i'm including the link after the code below :
//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {

private TemplatedListItem item;
private object commandSource;

public TemplatedListCommandEventArgs(TemplatedListItem item, object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}

public TemplatedListItem Item {
get {
return item;
}
}

public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)




//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish-------------- --
--------------------------------------

now in your page where the control sits you have the event handler use it,
all events fired in your templates can be captured here --you now have one
unique place to search for events as it is with templated controls, you can
have many controls in there that fire events, and you do not want an event
handler for every control in there, instead you want ONE handler that will
handle all --the trick it is to use the commandName for your buttons and
then look for this command name here --that way you can differentiate
between buttons and take action accordingly. So on the button that you have
used in your template pass use the CommandName property to like say if you
set the CommandName="save" then you can check if the save button was the one
clicked, see code below :

protected void MyList_ItemCreated(object sender, TemplatedListItemEventArgs
e) {

if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}
}

This is the templated databound sample, surely you will learn a lot besides
the event bubbling ;P
Ok --please look at the sample code on MSDN.

If you have the docs then the link is :
ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpcontemplateddataboundcontrolsampl
http://msdn.microsoft.com/library/d...html/cpcontemplateddataboundcontrolsample.asp

Not very hard to follow once you get a hang of it ;)





Peter Blum said:
Right now you're dealing with the ID property, as the error message tells
you.

If you do not assign an ID property, ASP.NET will automatically assign one
for your. However, if you need to refer to that ID in some other control,
like in Validator.ControlToValidator, that doesn't help.

I don't see your code that creates the validators and textboxes. So I don't
know how you are assigning Ids there. But I can see how you are assigning
the ID to the table. Perhaps we can learn from that:
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;


Don't assign an ID to either UniqueID or ClientID properties. The ID should
be a unique name within the naming container. ASP.NET will convert it
into
a
unique ID in ClientID and UniqueID. Additionally, UniqueID introduces a
format that doesn't work well in the client ID side attribute. (When you see
the HTML <input type='text' id=[from ClientID] name=[from UniqueID] />)

Here's what I like to do for IDs of child controls within a custom control.
Use the ID of custom control + "_" + some name.
For example:
HtmlTable table = new HtmlTable();
table.ID = this.ID + "_Table";

Because your custom control's ID will already be unique within the naming
container, so will its child controls.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen
 
A

Alessandro Zifiglio

Stephen, Event bubbling is what you want. That is bubble any events fired by
any control within the template and take it up to the container. Because any
control can be within your template --you will require one handler, where
you can check who fired the event and take action. The events are not firing
in your case coz as i said that code on MSDN is buggy. My coding language of
preference is VB.NET so all corrections i have made are in vb.net ;P

However one possible reason why the event is not firing is because of the
following line, or atleast i think --
protected virtual void OnItemCommand(NestedChildCommandEventArgs e) {
NestedChildCommandEventHandler onItemCommandHandler =
(NestedChildCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this,
e);
}

Change it to :

protected virtual void OnItemCommand(NestedChildCommandEventArgs e) {
if (EventItemCommand != null)
{
EventItemCommand(this,e);
}

Events should be bubbled up now and it should fire --hopefully ;P

Nice demo by the way --very clever of you to use tracing to showcase what is
happening --however I do not know what your code looks like and if i saw it
then i'd have to run tests --and that will be time consuming. Looks like you
are almost there. Try checking some old posts on the google GROUPS section.
PeterBlum has brought up this topic regarding the CreateChildControls method
a long time ago--see if any of the questions he brough up answers your
questions. In one of the old posts ofcourse. I'm sure its all in there.

That error you are getting with the viewstate could be because you are not
rebuilding your controls exactly how it were before postback
Make sure all controls are added exactly the way they were on the initial
request, just like how the error states ;P
I'm not much help there :)

Glad I could help.
Stephen Miller said:
Aleddandro,

I can't override the OnBubbleEvent method in NestedChildCollection
because it inherits from CollectionBase. However, following your logic
and the MSDN sample, I have gone ahead and overridden the
OnBubbleEvent in the template NestedChild, which inherits from Control
as well as the container, Nested. I've also added a new sealed class
NestedChildCommandEventArgs inheriting from CommandEventArgs and it's
deligate NestedChildCommandEventHandler.

I've added page tracing to the OnBubbleEvent method in the Nested and
NestedChild objects, however this doesn't appear to be called. Is
event bubbling useful only when I know in advance what server controls
are being nested in my control? The primary goal of my control is to
allow the developer to place any literal or control within the
NestedChild object and have them encapsulated within the Nested
object's formatting (in this case a table).

To demonstrate, I have put a sample (which hopefully will make clear
what I'm trying to achieve) at www.3la.com .au/tabs.aspx.

If my nested container contains a Button control or a RadioButtonList
or a DataGrid then, CreateChildControls is processed in the PreRender
phase as expected.

However, If my nested container contains a Button control, TextBox and
its associated RequiredFieldValidator then CreateChildControls is
called in the ProcessPostData phase before the SelectedIndexChanged
event is handled in PostBackEvent.

As such, it appears that under some circumstances CreateChildControls
is called in the ProcessPostData phase, rather then in the PreRender
phase of the control life cycle.

The MSDN guide to the control life cycle
(ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpconcontrolexecutionlifecycle.htm
)
does not list the CreateChildControls method "because it is called
whenever the ASP.NET page framework needs to create the controls tree
and this method call is not limited to a specific phase in a control's
lifecycle. For example, CreateChildControls can be invoked when
loading a page, during data binding, or during rendering".

Of course this is very unhelpful if CreateChildControls depends on a
PostBackEvent.

I have found another reference to this problem (without solution) at
http://support.softartisans.com/kbview.aspx?ID=671 (see under the
apply named section, 'take heed')

So how do I control when CreateChildControls is called?

I had thought that I could call CreateChildControls (twice if
necessary) from onPreRender (after we have handled
RaisePostBackEvent), but this results in "Multiple controls with the
same ID" error. I don't understand this as CreateChildControls
includes a Controls.Clear() statement, which I assumed would reset the
control's naming hierarchy).

I then tried moving CreateChildControls to
IPostBackEventHandler.RaisePostBackEvent and bizarrely this fixed the
problem with CreateChildControls being called once, in the
PostBackEvent phase. Again I don't understand why this actually works,
given that the ProcessPostData phase (where CreateChildControls was
being called during some PostBack events) happens before the
PostBackEvent phase (Note the sample referred to above doesn't have
this workaround).

Moving right along, I've now encountered a problem with a DataGrid
nested in my template container. When I click away, I'm now getting
the error:

"Failed to load viewstate. The control tree into which viewstate is
being loaded must match the control tree that was used to save
viewstate during the previous request. For example, when adding
controls dynamically, the controls added during a post-back must match
the type and position of the controls added during the initial
request."

I'll start on this problem tomorrow ;)

Stephen


"Alessandro Zifiglio" <[email protected]> wrote in
message news: said:
Stephen, when I made that last post, I was in a hurry and missed out a few
things. I have given the code another look and here are some corrections.
However note that I have not taken the time to test this with your code, but
that i have just copied and pasted the essential parts that you need to
implement, and I have tried to adapt it to your code.

Also note that the example on MSDN if you tried to compile it along with its
designer class --you will end up with errors, this is because there are
numerous bugs in the code and i had to make many corrections before i was
able to run that code myself --however it demonstrates how to code and puts
you in the right direction on many techniques like with using
templates --databinding --associating a designer to your control, event
bubbling etc

Here is a much better step by step --the one i did last I missed out a few
things like declaring the static event EventItemCommand as object and i
removed some un-necessary code --Still did not test --the rest is up to you
to get this working ;)

//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {
private NestedChildCollection item;
private object commandSource;

public TemplatedListCommandEventArgs(NestedChildCollection item,
object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}


public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first declare the static event variable
EventItemCommand as object
//Next Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)

private static readonly object EventItemCommand = new object();



//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish--------------
--
--------------------------------------


protected void MyList_ItemCreated(object sender,
TemplatedListCommandEventArgs e) {
if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}

}


Alessandro Zifiglio said:
Now that the naming issue has been resolved by PeterBlum --your second
problem is the events being fired in your nestedchild controls. For any
events that fire in your template(nestedchild) you need to bubble them
up
to
the container(nested), you can then either take action in nested if
this
is
the last destination or bubble them futher up to the page, that is the
container of nested.

to handle or to raise the bubbled event, you must override the OnBubbleEvent
method for your control.

Now always building up on the same code you posted --the first one
that
is,
you have a button in your template --the clickevent for the button
will
fire
in your template --override the OnBubbleEvent method there and have it fire
in the container "nested" --in nested override the OnBUbbleEvent again and
send it up to its container(the page where the control sits)
You do not need to implement the IPostBackEventHandler for this. There
is
a
very nice example on msdn which goes into all the details. However its very
vast so i'm coping out the steps you need to take. For more look at the
example to which i'm including the link after the code below :
//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we
passed
the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {

private TemplatedListItem item;
private object commandSource;

public TemplatedListCommandEventArgs(TemplatedListItem item, object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}

public TemplatedListItem Item {
get {
return item;
}
}

public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)




//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null)
onItemCommandHandler(this,
e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}
------------------------------------------------------Finish--------------
--
--------------------------------------

now in your page where the control sits you have the event handler use it,
all events fired in your templates can be captured here --you now have one
unique place to search for events as it is with templated controls,
you
can
have many controls in there that fire events, and you do not want an event
handler for every control in there, instead you want ONE handler that will
handle all --the trick it is to use the commandName for your buttons and
then look for this command name here --that way you can differentiate
between buttons and take action accordingly. So on the button that you have
used in your template pass use the CommandName property to like say if you
set the CommandName="save" then you can check if the save button was
the
one
clicked, see code below :

protected void MyList_ItemCreated(object sender, TemplatedListItemEventArgs
e) {

if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}
}

This is the templated databound sample, surely you will learn a lot besides
the event bubbling ;P
Ok --please look at the sample code on MSDN.

If you have the docs then the link is :
ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpcontemplateddataboundcontrolsampl
http://msdn.microsoft.com/library/d...html/cpcontemplateddataboundcontrolsample.asp
Not very hard to follow once you get a hang of it ;)





Right now you're dealing with the ID property, as the error message tells
you.

If you do not assign an ID property, ASP.NET will automatically
assign
one
for your. However, if you need to refer to that ID in some other control,
like in Validator.ControlToValidator, that doesn't help.

I don't see your code that creates the validators and textboxes. So
I
don't
know how you are assigning Ids there. But I can see how you are assigning
the ID to the table. Perhaps we can learn from that:
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;


Don't assign an ID to either UniqueID or ClientID properties. The ID should
be a unique name within the naming container. ASP.NET will convert
it
into
a
unique ID in ClientID and UniqueID. Additionally, UniqueID introduces a
format that doesn't work well in the client ID side attribute. (When
you
see
the HTML <input type='text' id=[from ClientID] name=[from UniqueID] />)

Here's what I like to do for IDs of child controls within a custom control.
Use the ID of custom control + "_" + some name.
For example:
HtmlTable table = new HtmlTable();
table.ID = this.ID + "_Table";

Because your custom control's ID will already be unique within the naming
container, so will its child controls.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen
 
S

Stephen Miller

Alessandro,

Thanks for your help, I'm now getting EventBubbling.

I also solved my viewstate problem (see
http://www.denisbauer.com/ASPNETControls/DynamicControlsPlaceholder.aspx
for a how-too) and everything works well now.

This has certainly been a challenging introduction to controls. At
some stage I'll blog a summary of the process.

Regards,

Stephen




Alessandro Zifiglio said:
Stephen, Event bubbling is what you want. That is bubble any events fired by
any control within the template and take it up to the container. Because any
control can be within your template --you will require one handler, where
you can check who fired the event and take action. The events are not firing
in your case coz as i said that code on MSDN is buggy. My coding language of
preference is VB.NET so all corrections i have made are in vb.net ;P

However one possible reason why the event is not firing is because of the
following line, or atleast i think --
protected virtual void OnItemCommand(NestedChildCommandEventArgs e) {
NestedChildCommandEventHandler onItemCommandHandler =
(NestedChildCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this,
e);
}

Change it to :

protected virtual void OnItemCommand(NestedChildCommandEventArgs e) {
if (EventItemCommand != null)
{
EventItemCommand(this,e);
}

Events should be bubbled up now and it should fire --hopefully ;P

Nice demo by the way --very clever of you to use tracing to showcase what is
happening --however I do not know what your code looks like and if i saw it
then i'd have to run tests --and that will be time consuming. Looks like you
are almost there. Try checking some old posts on the google GROUPS section.
PeterBlum has brought up this topic regarding the CreateChildControls method
a long time ago--see if any of the questions he brough up answers your
questions. In one of the old posts ofcourse. I'm sure its all in there.

That error you are getting with the viewstate could be because you are not
rebuilding your controls exactly how it were before postback
Make sure all controls are added exactly the way they were on the initial
request, just like how the error states ;P
I'm not much help there :)

Glad I could help.
Stephen Miller said:
Aleddandro,

I can't override the OnBubbleEvent method in NestedChildCollection
because it inherits from CollectionBase. However, following your logic
and the MSDN sample, I have gone ahead and overridden the
OnBubbleEvent in the template NestedChild, which inherits from Control
as well as the container, Nested. I've also added a new sealed class
NestedChildCommandEventArgs inheriting from CommandEventArgs and it's
deligate NestedChildCommandEventHandler.

I've added page tracing to the OnBubbleEvent method in the Nested and
NestedChild objects, however this doesn't appear to be called. Is
event bubbling useful only when I know in advance what server controls
are being nested in my control? The primary goal of my control is to
allow the developer to place any literal or control within the
NestedChild object and have them encapsulated within the Nested
object's formatting (in this case a table).

To demonstrate, I have put a sample (which hopefully will make clear
what I'm trying to achieve) at www.3la.com .au/tabs.aspx.

If my nested container contains a Button control or a RadioButtonList
or a DataGrid then, CreateChildControls is processed in the PreRender
phase as expected.

However, If my nested container contains a Button control, TextBox and
its associated RequiredFieldValidator then CreateChildControls is
called in the ProcessPostData phase before the SelectedIndexChanged
event is handled in PostBackEvent.

As such, it appears that under some circumstances CreateChildControls
is called in the ProcessPostData phase, rather then in the PreRender
phase of the control life cycle.

The MSDN guide to the control life cycle
(ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpconcontrolexecutionlifecycle.htm
)
does not list the CreateChildControls method "because it is called
whenever the ASP.NET page framework needs to create the controls tree
and this method call is not limited to a specific phase in a control's
lifecycle. For example, CreateChildControls can be invoked when
loading a page, during data binding, or during rendering".

Of course this is very unhelpful if CreateChildControls depends on a
PostBackEvent.

I have found another reference to this problem (without solution) at
http://support.softartisans.com/kbview.aspx?ID=671 (see under the
apply named section, 'take heed')

So how do I control when CreateChildControls is called?

I had thought that I could call CreateChildControls (twice if
necessary) from onPreRender (after we have handled
RaisePostBackEvent), but this results in "Multiple controls with the
same ID" error. I don't understand this as CreateChildControls
includes a Controls.Clear() statement, which I assumed would reset the
control's naming hierarchy).

I then tried moving CreateChildControls to
IPostBackEventHandler.RaisePostBackEvent and bizarrely this fixed the
problem with CreateChildControls being called once, in the
PostBackEvent phase. Again I don't understand why this actually works,
given that the ProcessPostData phase (where CreateChildControls was
being called during some PostBack events) happens before the
PostBackEvent phase (Note the sample referred to above doesn't have
this workaround).

Moving right along, I've now encountered a problem with a DataGrid
nested in my template container. When I click away, I'm now getting
the error:

"Failed to load viewstate. The control tree into which viewstate is
being loaded must match the control tree that was used to save
viewstate during the previous request. For example, when adding
controls dynamically, the controls added during a post-back must match
the type and position of the controls added during the initial
request."

I'll start on this problem tomorrow ;)

Stephen


"Alessandro Zifiglio" <[email protected]> wrote in
message news: said:
Stephen, when I made that last post, I was in a hurry and missed out a few
things. I have given the code another look and here are some corrections.
However note that I have not taken the time to test this with your code, but
that i have just copied and pasted the essential parts that you need to
implement, and I have tried to adapt it to your code.

Also note that the example on MSDN if you tried to compile it along with its
designer class --you will end up with errors, this is because there are
numerous bugs in the code and i had to make many corrections before i was
able to run that code myself --however it demonstrates how to code and puts
you in the right direction on many techniques like with using
templates --databinding --associating a designer to your control, event
bubbling etc

Here is a much better step by step --the one i did last I missed out a few
things like declaring the static event EventItemCommand as object and i
removed some un-necessary code --Still did not test --the rest is up to you
to get this working ;)

//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we passed the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {
private NestedChildCollection item;
private object commandSource;

public TemplatedListCommandEventArgs(NestedChildCollection item,
object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}


public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first declare the static event variable
EventItemCommand as object
//Next Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)

private static readonly object EventItemCommand = new object();



//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish--------------
--
--------------------------------------


protected void MyList_ItemCreated(object sender,
TemplatedListCommandEventArgs e) {
if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}
}


message Now that the naming issue has been resolved by PeterBlum --your second
problem is the events being fired in your nestedchild controls. For any
events that fire in your template(nestedchild) you need to bubble them
up
to
the container(nested), you can then either take action in nested if
this
is
the last destination or bubble them futher up to the page, that is the
container of nested.

to handle or to raise the bubbled event, you must override the OnBubbleEvent
method for your control.

Now always building up on the same code you posted --the first one
that
is,
you have a button in your template --the clickevent for the button
will
fire
in your template --override the OnBubbleEvent method there and have it fire
in the container "nested" --in nested override the OnBUbbleEvent again and
send it up to its container(the page where the control sits)
You do not need to implement the IPostBackEventHandler for this. There
is
a
very nice example on msdn which goes into all the details. However its very
vast so i'm coping out the steps you need to take. For more look at the
example to which i'm including the link after the code below :
//First bubble events in NestedChildCollection
//Note how it looks only for a CommandEventArgs
//and passes that information to the
//class TemplatedListCommandEventArgs which will in turn delegate
//the event.
public class NestedChildCollection : CollectionBase {

public NestedChild this[int nIndex]{
get { return (NestedChild) base.List[nIndex]; }
}
public void Add(NestedChild child){
base.List.Add(child);
}

public int IndexOf(NestedChild child){
return base.List.IndexOf(child);
}
}

protected override bool OnBubbleEvent(object source, EventArgs e) {
if (e is CommandEventArgs) {
// Add the information about Item to CommandEvent.

TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source,
(CommandEventArgs)e);

RaiseBubbleEvent(this, args);
return true;
}
return false;
}

}


//Now the class TemplatedListCommandEventArgs --this is where we
passed
the
captured event, and now we delegate it.

public sealed class TemplatedListCommandEventArgs : CommandEventArgs {

private TemplatedListItem item;
private object commandSource;

public TemplatedListCommandEventArgs(TemplatedListItem item, object
commandSource, CommandEventArgs originalArgs) :
base(originalArgs) {
this.item = item;
this.commandSource = commandSource;
}

public TemplatedListItem Item {
get {
return item;
}
}

public object CommandSource {
get {
return commandSource;
}
}
}

public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);


//now in your class(nested) first Override OnBubbleEvent method and
//bubble the events to the container(that is the page the control sits)




//note how OnItemCommand is called when the TemplatedListCommandEventHandler
//is the one that fired --the one that we bubbled
//in the Template

protected override bool OnBubbleEvent(object source, EventArgs e) {
// Handle events raised by children by overriding OnBubbleEvent.

bool handled = false;

if (e is TemplatedListCommandEventArgs) {
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;

OnItemCommand(ce);
handled = true;
}

return handled;
}

//now the OnItemCommand --the one that we called in the above method :
//this will raise the bubbled Event to the client with
//the handler and all

protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) {
TemplatedListCommandEventHandler onItemCommandHandler =
(TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null)
onItemCommandHandler(this,
e);
}


//expose the handler TemplatedListCommandEventHandler to the client(the
page)

[
Category("Action"),
Description("Raised when a CommandEvent occurs within the
Template.")
]
public event TemplatedListCommandEventHandler ItemCommand {
add {
Events.AddHandler(EventItemCommand, value);
}
remove {
Events.RemoveHandler(EventItemCommand, value);
}
}



------------------------------------------------------Finish--------------
--
--------------------------------------

now in your page where the control sits you have the event handler use it,
all events fired in your templates can be captured here --you now have one
unique place to search for events as it is with templated controls,
you
can
have many controls in there that fire events, and you do not want an event
handler for every control in there, instead you want ONE handler that will
handle all --the trick it is to use the commandName for your buttons and
then look for this command name here --that way you can differentiate
between buttons and take action accordingly. So on the button that you have
used in your template pass use the CommandName property to like say if you
set the CommandName="save" then you can check if the save button was
the
one
clicked, see code below :

protected void MyList_ItemCreated(object sender, TemplatedListItemEventArgs
e) {

if (e.CommandName == "save"){
response.write("save button was clicked, lets do something")
}
}

This is the templated databound sample, surely you will learn a lot besides
the event bubbling ;P
Ok --please look at the sample code on MSDN.

If you have the docs then the link is :

ms-help://MS.VSCC/MS.MSDNVS/cpguide/html/cpcontemplateddataboundcontrolsampl
e.htm


http://msdn.microsoft.com/library/d...html/cpcontemplateddataboundcontrolsample.asp

Not very hard to follow once you get a hang of it ;)





Right now you're dealing with the ID property, as the error message tells
you.

If you do not assign an ID property, ASP.NET will automatically
assign
one
for your. However, if you need to refer to that ID in some other control,
like in Validator.ControlToValidator, that doesn't help.

I don't see your code that creates the validators and textboxes. So
I
don't
know how you are assigning Ids there. But I can see how you are assigning
the ID to the table. Perhaps we can learn from that:
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;


Don't assign an ID to either UniqueID or ClientID properties. The ID should
be a unique name within the naming container. ASP.NET will convert it
into
a
unique ID in ClientID and UniqueID. Additionally, UniqueID introduces a
format that doesn't work well in the client ID side attribute. (When
you
see
the HTML <input type='text' id=[from ClientID] name=[from UniqueID] />)

Here's what I like to do for IDs of child controls within a custom control.
Use the ID of custom control + "_" + some name.
For example:
HtmlTable table = new HtmlTable();
table.ID = this.ID + "_Table";

Because your custom control's ID will already be unique within the naming
container, so will its child controls.

--- Peter Blum
www.PeterBlum.com
Email: (e-mail address removed)

Peter,

Again, thanks for your on going help.

I have been struggling with this problem for 3+ months and on someone
else's advice purchased "Developing Microsoft ASP.NET Server Controls
and Components". Following the book religiously, I started again from
the "Hello World" example and worked forward to recreate my problem.
Kothari & Datye's touches on controls "whose nested content does not
correspond to properties" on pages 332-7, but the example provided is
very trivial and doesn't address any of the issues I've encountered.
If I've missed something, please point me to the section.

My control uses the ParseChildrenAttribute attribute with the
declaration '[ParseChildren(true, "NestedChild")', which defines a
public property named 'NestedChild', with nested (child) elements
corresponding to child elements of the 'NestedChild' property. The
idea with the '_NestedChildren' property is to add each nested child
element to a collection, which I can iterate through and hide or show
based on additional conditions. I have extra logic here, which I have
omitted for clarity.

As you suggested, I have move my code to CreateChildControls. The
control renders ok in design time, but fails in runtime at
FillNamedControlsTable with the error message "Multiple controls with
the same ID 'myControl1' were found. FindControl requires that
controls have unique IDs". With the RequiredFieldValidator removed,
the control renders but generates the same error with the buttons
onClick event. Looking at the source code I notice that the command
button has now rendered as:

<input type="submit" name="myControl1:cmdTest1" value="Test"
id="myControl1_cmdTest1" />

I can't compile my code behind with an event handling
myControl1_cmdTest1.Click, because the compiler is expecting
cmdTest1.click.

My code for CreateChildControls now looks like:

protected override void CreateChildControls() {

this.Controls.Clear();
this.Controls.Add(new LiteralControl("<b>before</b><br /><hr><br
/>"));

foreach (NestedChild child in _NestedChildren) {
//this.Controls.Add(child);

this.Controls.Add(new LiteralControl("* " + child.Caption +
"<BR>"));

// Display the contents of child in a single cell table
HtmlTableCell td = new HtmlTableCell();
td.Style.Add("width", "250px");
td.Style.Add("height", "100px");
td.Controls.Add(child);

// Add the table cell to a new table row
HtmlTableRow tr = new HtmlTableRow();
tr.Controls.Add(td);

// Add the table row to a new table
HtmlTable table = new HtmlTable();
table.ID = this.UniqueID;
table.Width = this.Width.ToString();
table.Height = this.Height.ToString();
table.CellSpacing = 2;
table.CellPadding = 2;
table.Border = 1;
table.Style.Add("background-color", "#C0C0C0");
table.Controls.Add(tr);

this.Controls.Add(table);

}
this.Controls.Add(new LiteralControl("<br /><hr><br
/><b>after</b>"));
}

Any other suggestions?

Regards,

Stephen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,734
Messages
2,569,441
Members
44,832
Latest member
GlennSmall

Latest Threads

Top