Custom web control not responding to change

N

~~~ .NET Ed ~~~

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;
 
N

~~~ .NET Ed ~~~

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 ~~~ said:
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;
 
B

Brock Allen

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
{
....
}
 
N

~~~ .NET Ed ~~~

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?
 
B

Brock Allen

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

~~~ .NET Ed ~~~

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

Brock Allen

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.

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?
 
N

~~~ .NET Ed ~~~

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?
 
B

Brock Allen

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);
}

}
}
 
N

~~~ .NET Ed ~~~

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
 

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,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top