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

Discussion in 'ASP .Net Building Controls' started by Stephen Miller, Jan 3, 2004.

  1. 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
     
    Stephen Miller, Jan 3, 2004
    #1
    1. Advertisements

  2. 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



     
    Stephen Miller, Jan 4, 2004
    #2
    1. Advertisements

  3. 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

     
    Stephen Miller, Jan 7, 2004
    #3
  4. 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


     
    Stephen Miller, Jan 11, 2004
    #4
  5. have your control implement INamingContainer. This will generate Unique Ids
     
    Alessandro Zifiglio, Jan 15, 2004
    #5
  6. 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, Jan 15, 2004
    #6
  7. 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


     
    Stephen Miller, Jan 17, 2004
    #7
  8. 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

     
    Stephen Miller, Jan 17, 2004
    #8
  9. 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 ;)





     
    Alessandro Zifiglio, Jan 17, 2004
    #9
  10. 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, Jan 19, 2004
    #10
  11. 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


     
    Stephen Miller, Jan 19, 2004
    #11
  12. 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.
     
    Alessandro Zifiglio, Jan 19, 2004
    #12
  13. 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




     
    Stephen Miller, Jan 20, 2004
    #13
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.