Custom web control not responding to change

Discussion in 'ASP .Net Building Controls' started by ~~~ .NET Ed ~~~, Mar 14, 2005.

  1. Hi,
    Since I ran into a problem with nested user web controls I decided I was
    (???) better off implementing this as a custom web control. Well, I have run
    into problems again... I created a new project on the solution that contains
    the web page. That project is a Web Control Library type of project. I
    created the new custom web control and can drag it to a page and it
    functions but does not work as desired (see below).

    To simplify the explanation, this custom web control is a composite control
    with at least two drop down lists, say Countries and States. So far so good,
    when the page is shown I see both drop down lists showing. The first one
    (Countries) is data-bound and correctly reads the list (and populates the
    drop down) of countries.

    I have also defined a handler for the "selected item changed" of the
    countries drop down but somehow it does not seem to be triggered. The drop
    down list has its AutoPostBack set to true and I am able to see that when I
    change the selection there IS a postback, only my event handler is not being
    called.

    [DefaultProperty("ShowExtendedLocation"),

    ToolboxData("<{0}:WebLocationControl
    runat=server></{0}:WebLocationControl>")]

    public class WebLocationControl : System.Web.UI.WebControls.WebControl

    {
    private DropDownList dlCountry;private DropDownList dlProvince;
     
    ~~~ .NET Ed ~~~, Mar 14, 2005
    #1
    1. Advertisements

  2. This stupid OE posted the message before I was finished. Anyway, I already
    described what the problem is:

    - When the main drop down list changes selection, its dependent
    (province/state) drop down list is not
    populated because somehow the selection changed event is not being
    triggered (a postback however
    IS observed).

    Here is the code of the simple (yet non-working) custom web control in hopes
    that somebody can tell
    me what I am doing wrong (as you see it is a composite web control)

    ---- Beginning of sample code

    using System;
    using System.Data.SqlClient;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.HtmlControls;
    using System.ComponentModel;

    namespace Coralys.CtRealty.CustomWebControls
    {
    /// <summary>
    /// Summary description for WebLocationControl.
    /// </summary>
    [DefaultProperty("ShowExtendedLocation"),
    ToolboxData("<{0}:WebLocationControl
    runat=server></{0}:WebLocationControl>")]
    public class WebLocationControl : System.Web.UI.WebControls.WebControl
    {
    private string text = "Version 0.1.1";
    private DropDownList dlCountry;
    private DropDownList dlProvince;

    #region Properties
    [Bindable(true), Category("Appearance"), DefaultValue("")]
    public string Text
    {
    get { return text; }
    set { text = value; }
    }
    #endregion

    protected override void CreateChildControls()
    {
    base.CreateChildControls();
    this.dlCountry = new DropDownList();
    this.dlCountry.ID = this.ID + "_Country";
    this.dlCountry.SelectedIndexChanged += new
    EventHandler(dlCountry_SelectedIndexChanged);
    this.dlCountry.AutoPostBack = true;
    this.dlProvince = new DropDownList();
    this.dlProvince.ID = this.ID + "_Province";
    this.dlProvince.AutoPostBack = true;
    }

    /// <summary>
    /// Render this control to the output parameter specified.
    /// </summary>
    /// <param name="output"> The HTML writer to write out to </param>
    protected override void Render(HtmlTextWriter output)
    {
    HtmlTable tbl = new HtmlTable();
    tbl.CellSpacing = 0;
    tbl.CellPadding = 0;
    tbl.Border = 0;

    HtmlTableRow tr = new HtmlTableRow();
    HtmlTableCell td = new HtmlTableCell();

    // Country Row
    td.InnerText = "Country";
    tr.Controls.Add(td);
    td = new HtmlTableCell();
    td.Controls.Add(this.dlCountry);
    tr.Controls.Add(td);
    tbl.Controls.Add(tr);

    // Province Row
    tr = new HtmlTableRow();
    td = new HtmlTableCell();
    td.InnerText = "Province";
    tr.Controls.Add(td);
    td = new HtmlTableCell();
    td.Controls.Add(this.dlProvince);
    tr.Controls.Add(td);
    tbl.Controls.Add(tr);

    this.Controls.Add(tbl);

    PopulateLists();
    tbl.RenderControl(output);
    }

    private void PopulateLists()
    {
    // Load country information
    Location entLocation = new Location();
    string text, val;
    System.Data.SqlClient.SqlDataReader dr = entLocation.BindCountries(out
    text, out val);
    this.dlCountry.DataSource = dr;
    this.dlCountry.DataTextField = text;
    this.dlCountry.DataValueField = val;
    this.dlCountry.DataBind();
    this.dlCountry.SelectedIndex = 0; // select first and trigger provinces
    dr.Close();
    }

    #region Event Handlers
    /// <summary>
    /// When the country selection changes we fill up the Province list
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void dlCountry_SelectedIndexChanged(object sender,
    System.EventArgs e)
    {
    this.dlProvince.Items.Clear(); // get rid of old ones
    Location entLocation = new Location();
    string text, val;
    System.Data.SqlClient.SqlDataReader dr =
    entLocation.BindProvinces(this.dlCountry.SelectedValue,out text, out val);
    this.dlProvince.DataSource = dr;
    this.dlProvince.DataTextField = text;
    this.dlProvince.DataValueField = val;
    this.dlProvince.DataBind();
    dr.Close();
    this.dlProvince.SelectedIndex = 0;
    }
    #endregion
    }
    }

    ---- End of sample code
     
    ~~~ .NET Ed ~~~, Mar 14, 2005
    #2
    1. Advertisements

  3. ~~~ .NET Ed ~~~

    Brock Allen Guest

    Have your custom control class implement the INamingContainer interface.
    It has no methods, so it's just a matter of adding it into the interface
    list.

    class YourControl: WebControl, INamingContainer
    {
    ....
    }
     
    Brock Allen, Mar 15, 2005
    #3
  4. It already has it, I added just a few minutes after the post. Unfortunately
    that does not seem to have changed anything. I see a postback still occurrs
    but the selection changed event is still not triggered. Any other ideas?
     
    ~~~ .NET Ed ~~~, Mar 15, 2005
    #4
  5. ~~~ .NET Ed ~~~

    Brock Allen Guest

    Hmm, I think I see the problem -- why defer creating the Table until Render?
    I think what's happening is that when the postback happens you don't have
    the same control tree build that you rendered. I must say, it's a bit odd
    for me to see you creating the Table in Render and not in CreateChildControls.
    When the postback happens I have a feeling it can't find your dropdown list
    at the right place in the hierarchy to tell it that it's changed. I'd suggest
    simplifying and moving all the control creation to CreateChildControls and
    get rid of Render.
     
    Brock Allen, Mar 15, 2005
    #5
  6. I also tried moving the HtmlTable stuff to the CreateChildControls() as you
    mentioned (minus the tbl.RenderControl statement) and the result was that
    the whole thing simply disappeared, the Table (nor the drop down controls)
    were being drawn anymore... I am puzzled.
     
    ~~~ .NET Ed ~~~, Mar 15, 2005
    #6
  7. ~~~ .NET Ed ~~~

    Brock Allen Guest

    I also tried moving the HtmlTable stuff to the CreateChildControls()
    Hmm, well, just make sure that you add any controls into the this.Controls
    collection (via Add, which I did see in your code elsewhere). Essentially
    any dynamically created control needs to fit into the server side tree of
    controls, so it needs to be under your custom control in the tree or under
    some other control that has your control as the ultimate parent. That's why
    overriding Redner is usually unnecessary, since as long as the control is
    in the tree somewhere, the default Render implementation in the base class
    just walks throught the tree calling Render on the individual controls.

    So, I'm puzzled too :) Perhaps posting the version of your code where it's
    all moved into CreateChildControls?
     
    Brock Allen, Mar 15, 2005
    #7
  8. Now, something even more puzzling.... I now have the following construction:

    CreateChildControls()
    {
    .... create controls ....
    // Draw the table
    DrawControl(null); // Place #1
    }

    Render(HtmlTextWriter writer)
    {
    writer.WriteLine("<b>Test</b>");
    DrawControl(writer); // Place #2
    }

    internal DrawControl(HtmlTextWriter writer)
    {
    HtmlTable tbl = new HtmlTable();
    .... build the whole table stuff and add the controls in the
    respective cells, just as before ....
    }

    I had created the DrawControl originally for the purpose of using it for the
    control designer (which by the way only displays "Error creating control"
    and gives no tooltip to hint the problem).

    So, the DrawControl() method is the one that actually builds up the HTML
    Table and stuffs it with the controls
    that were instantiated in first phase of CreateChildControls(). This is what
    I observed:

    1. When DrawControl() is called only in Render the control displays
    completely BUT it is unable to respond to
    the SelectedIndexChanged and therefore it does not populate the
    Province drop down. i.e. not good

    2. When DrawControl() is only called from CreateChildControls() AFTER the
    controls have been instantiated,
    the control simply shows up as blank. No table, no drop downs,
    NOTHING... i.e. even worse.

    3. When I call DrawControl() from BOTH CreateChildControls() AND Render(),
    then not only does the
    controls is properly displayed (the whole table and all the drop
    downs), it also WORKS correctly! that
    means that in this case changing the Country selection actually
    repopulates the 2nd drop down!!!

    Now, #3 seems wrong to me because the "drawing" is called in two places
    (does not seem logical to me) but somehow that is how it actually functions
    well. Any ideas why?
     
    ~~~ .NET Ed ~~~, Mar 15, 2005
    #8
  9. ~~~ .NET Ed ~~~

    Brock Allen Guest

    If you override CreateChildControls, "new" up the control objects, then Add()
    then to your Controls collection you should never have to call any sort of
    Render or Draw method on the controls -- this is done for you. If you want
    designer support there's a different way to get this. Perhaps we can start
    a different thread for that discussion.

    Here's a quick sample that does this (and doesn't call any explict Render
    method). Also, I have a feeling the code is going to look like crap given
    the formatting here... So sorry in advance:

    namespace DevelopMentor
    {
    public class CalcControl : Control, INamingContainer
    {
    public event EventHandler Add;

    public int Answer
    {
    get
    {
    int x = Int32.Parse(tb1.Text);
    int y = Int32.Parse(tb2.Text);
    int z = x + y;
    return z;
    }
    }

    void OnClick(object sender, EventArgs ea)
    {
    b.Text = Answer.ToString();
    if (Add != null)
    {
    Add(this, EventArgs.Empty);
    }
    }

    TextBox tb1;
    TextBox tb2;
    Label b;

    protected override void CreateChildControls()
    {
    tb1 = new TextBox();
    tb1.ID = "tb1";
    tb2 = new TextBox();
    tb2.ID = "tb2";
    Button button = new Button();
    button.Text = " = ";
    button.Click += new EventHandler(OnClick);

    LiteralControl c = new LiteralControl(" + ");

    b = new Label();

    Controls.Add(tb1);
    Controls.Add(c);
    Controls.Add(tb2);
    Controls.Add(button);
    Controls.Add(b);
    }

    }
    }
     
    Brock Allen, Mar 15, 2005
    #9
  10. Hey thanks, I had forgotten to remove the "Render" method override. Now it
    is working as it should with
    only the CreateChildControls() rather than drawing it in two places. Also,
    it now responds properly to the
    event and fills the other dropdown box accordingly. I also got the designer
    class working 100%, I think I
    will simply stay out of crappy Web User Controls and go for custom controls
    for composite things.

    Thanks,
    Emil
     
    ~~~ .NET Ed ~~~, Mar 15, 2005
    #10
    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.