Dynamically Loading a UserControl

H

Harry

Hello,
I have a page with a RadioButtonList and a PlaceHolder control. The
RadioButtonList's AutoPostBack attribute is set to TRUE and its
SelectedIndexChanged event loads one of three UserControls into the
PlaceHolder's child control collection depending upon which of the three
radio buttons is selected. Each of the three UserControls have postback
events themselves triggered by button clicks. The problem I'm having is
keeping track of what UserControl to load upon a page being posted back. My
SelectedIndexChanged event will load a UserControl dynamically just fine,
however, in the event that the dynamically loaded user control itself issues
a postback the parnets Page_Load event must remeber which UserControl to
reload so it can handle its postback. In order to do this, I store the
loaded control's name in the ViewState. This solution causes a problem in
that moveing from one dynamically loaded UserControl to another will cause
two controls to be displayed at the same time. To fix this my
SelectedIndexChanged event will remove the first child control in the
PlaceHolder. However, this solution totally screws with my postback such
that they do not fire porperly. Here is an example of what I'm doing:

private void Page_Load(object sender, System.EventArgs e)
{
if (ViewState["ControlName"] != null)

PlaceHolderControl.Controls.Add(LoadControl(ViewState["ControlName"].ToStrin
g()));
}

private void MyRadioButtonList_SelectedIndexChanged(object sender,
System.EventArgs e)
{
if (PlaceHolderControl.HasControls())
PlaceHolderControl.Controls.RemoveAt(0);

ViewState["ControlName"] = MyRadioButtonList.SelectedValue;

PlaceHolderControl.Controls.Add(LoadControl(MyRadioButtonList.SelectedValue)
);
}

Any suggestions would be much appreciated.
- Harry
 
J

Jason Penniman

I did something similar in an application I wrote... I ended up placing all the controls on the page, hidden, then turned on the appropriate one.

The problem with your approach (which was my inital approach) is that the event code is fired first. So, the dynamic control doesn't exist when a parent event fires.

Here's what I did...
on the aspx page...

<asp:placeHolder ID="appArea" Runat="server"></asp:placeHolder>
<extn:companyFinder id="ctlCompanyFinder" runat="server" visible="false" />
<extn:company id="ctlCompany" runat="server" visible="false" />


then, in the code behind...
private void Page_Load(object sender, System.EventArgs e)
{

if (this.IsPostBack)
{
if (Session["_extn_control"] != null)
currentControl = this.FindControl(Session["_extn_control"].ToString());

if (currentControl != null)
{
currentControl.Visible = true;
}
}
}

protected void MenuItemHandler(object sender, System.EventArgs e)
{
MenuItem item = (MenuItem)sender;
switch (item.ID)
{
case "mnuFindCompany":
ctlCompanyFinder.Visible = true;
Session["_extn_control"] = "ctlCompanyFinder";
break;
}//end switch item

}//end MenuItemHandler

I used a session variable to trake the name of the current control, but you could certainly use viewstate.

Harry said:
Hello,
I have a page with a RadioButtonList and a PlaceHolder control. The
RadioButtonList's AutoPostBack attribute is set to TRUE and its
SelectedIndexChanged event loads one of three UserControls into the
PlaceHolder's child control collection depending upon which of the three
radio buttons is selected. Each of the three UserControls have postback
events themselves triggered by button clicks. The problem I'm having is
keeping track of what UserControl to load upon a page being posted back. My
SelectedIndexChanged event will load a UserControl dynamically just fine,
however, in the event that the dynamically loaded user control itself issues
a postback the parnets Page_Load event must remeber which UserControl to
reload so it can handle its postback. In order to do this, I store the
loaded control's name in the ViewState. This solution causes a problem in
that moveing from one dynamically loaded UserControl to another will cause
two controls to be displayed at the same time. To fix this my
SelectedIndexChanged event will remove the first child control in the
PlaceHolder. However, this solution totally screws with my postback such
that they do not fire porperly. Here is an example of what I'm doing:

private void Page_Load(object sender, System.EventArgs e)
{
if (ViewState["ControlName"] != null)

PlaceHolderControl.Controls.Add(LoadControl(ViewState["ControlName"].ToStrin
g()));
}

private void MyRadioButtonList_SelectedIndexChanged(object sender,
System.EventArgs e)
{
if (PlaceHolderControl.HasControls())
PlaceHolderControl.Controls.RemoveAt(0);

ViewState["ControlName"] = MyRadioButtonList.SelectedValue;

PlaceHolderControl.Controls.Add(LoadControl(MyRadioButtonList.SelectedValue)
);
}

Any suggestions would be much appreciated.
- Harry
 
H

Harry

Hello Again,

Quick update, I have added code to my Page_Load event such that it will only
load the control named in the ViewState if the PostBack was NOT raised by
the RadioButtonList. The updated code looks as follows:

if (ViewState["ControlName"] != null &&

Request["__EVENTTARGET"] != ModeRadioButtonList.ClientID + "_0" &&

Request["__EVENTTARGET"] != ModeRadioButtonList.ClientID + "_1" &&

Request["__EVENTTARGET"] != ModeRadioButtonList.ClientID + "_2")

This corrects the problem, however, an exception is thrown when switching
user controls to load by clicking various radio buttons in the list. The
exception reads as follows:

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.

Looks like my updated solution is not acceptable afterall. This is puzling
since the ASP.NET exception indicates the control must be reloaded during
the PostBack, however, I did this the first time around and then removed it
when the SelectedIndexChanged event occured and replaced it with the
requested UserControl, but that solution failed as well since the newly
added UserConrol did not handle PostBacks correctly (see my original code).

- Harry



Harry said:
Hello,
I have a page with a RadioButtonList and a PlaceHolder control. The
RadioButtonList's AutoPostBack attribute is set to TRUE and its
SelectedIndexChanged event loads one of three UserControls into the
PlaceHolder's child control collection depending upon which of the three
radio buttons is selected. Each of the three UserControls have postback
events themselves triggered by button clicks. The problem I'm having is
keeping track of what UserControl to load upon a page being posted back. My
SelectedIndexChanged event will load a UserControl dynamically just fine,
however, in the event that the dynamically loaded user control itself issues
a postback the parnets Page_Load event must remeber which UserControl to
reload so it can handle its postback. In order to do this, I store the
loaded control's name in the ViewState. This solution causes a problem in
that moveing from one dynamically loaded UserControl to another will cause
two controls to be displayed at the same time. To fix this my
SelectedIndexChanged event will remove the first child control in the
PlaceHolder. However, this solution totally screws with my postback such
that they do not fire porperly. Here is an example of what I'm doing:

private void Page_Load(object sender, System.EventArgs e)
{
if (ViewState["ControlName"] != null)

PlaceHolderControl.Controls.Add(LoadControl(ViewState["ControlName"].ToStrin
g()));
}

private void MyRadioButtonList_SelectedIndexChanged(object sender,
System.EventArgs e)
{
if (PlaceHolderControl.HasControls())
PlaceHolderControl.Controls.RemoveAt(0);

ViewState["ControlName"] = MyRadioButtonList.SelectedValue;

PlaceHolderControl.Controls.Add(LoadControl(MyRadioButtonList.SelectedValue)
);
}

Any suggestions would be much appreciated.
- Harry
 
H

Harry

Jason,
Thank you for the reply. I have tried your approach of putting all the
UserControls on the .aspx page and toggling the Visible attribute on
individual UserControls as needed. The only problem with this method is that
even though a page may not be visible, its Page_Load method is still called
for each UserControl on the page. This may or may not be a performance
problem depending on what initialization is going on in all the Page_Load
events. For instance, in your example, both "companyFinder" and "company"
are being loaded and perhaps populating DataTables with SQL data, however,
only one user control is being rendered. I suppose you could work around
this by using Session keys keep track of which control is to be loaded such
that Page_Load events will skip over their initialization code if the
selected control isn't specified by the Session key/value pair.

In the end I was thinking of having the RadioButtonList's
SelectedIndexChanged event do a Server.Transfer and apend a QueryString to
the URL, such as ...&View="SmallView.ascx" or ...&View="LargeView.ascx" that
identifies which control to load dynamically. In this case all the parent
..aspx page that loads the dynamic user control has to do is look at the
Request["View"] query string to know which control to load.

- Harry
I did something similar in an application I wrote... I ended up placing all
the controls on the page, hidden, then turned on the appropriate one.

The problem with your approach (which was my inital approach) is that the
event code is fired first. So, the dynamic control doesn't exist when a
parent event fires.

Here's what I did...
on the aspx page...

<asp:placeHolder ID="appArea" Runat="server"></asp:placeHolder>
<extn:companyFinder id="ctlCompanyFinder" runat="server" visible="false" />
<extn:company id="ctlCompany" runat="server" visible="false" />


then, in the code behind...
private void Page_Load(object sender, System.EventArgs e)
{

if (this.IsPostBack)
{
if (Session["_extn_control"] != null)
currentControl =
this.FindControl(Session["_extn_control"].ToString());

if (currentControl != null)
{
currentControl.Visible = true;
}
}
}

protected void MenuItemHandler(object sender, System.EventArgs e)
{
MenuItem item = (MenuItem)sender;
switch (item.ID)
{
case "mnuFindCompany":
ctlCompanyFinder.Visible = true;
Session["_extn_control"] = "ctlCompanyFinder";
break;
}//end switch item

}//end MenuItemHandler

I used a session variable to trake the name of the current control, but you
could certainly use viewstate.

Harry said:
Hello,
I have a page with a RadioButtonList and a PlaceHolder control. The
RadioButtonList's AutoPostBack attribute is set to TRUE and its
SelectedIndexChanged event loads one of three UserControls into the
PlaceHolder's child control collection depending upon which of the three
radio buttons is selected. Each of the three UserControls have postback
events themselves triggered by button clicks. The problem I'm having is
keeping track of what UserControl to load upon a page being posted back. My
SelectedIndexChanged event will load a UserControl dynamically just fine,
however, in the event that the dynamically loaded user control itself issues
a postback the parnets Page_Load event must remeber which UserControl to
reload so it can handle its postback. In order to do this, I store the
loaded control's name in the ViewState. This solution causes a problem in
that moveing from one dynamically loaded UserControl to another will cause
two controls to be displayed at the same time. To fix this my
SelectedIndexChanged event will remove the first child control in the
PlaceHolder. However, this solution totally screws with my postback such
that they do not fire porperly. Here is an example of what I'm doing:

private void Page_Load(object sender, System.EventArgs e)
{
if (ViewState["ControlName"] != null)

PlaceHolderControl.Controls.Add(LoadControl(ViewState["ControlName"].ToStrin
g()));
}

private void MyRadioButtonList_SelectedIndexChanged(object sender,
System.EventArgs e)
{
if (PlaceHolderControl.HasControls())
PlaceHolderControl.Controls.RemoveAt(0);

ViewState["ControlName"] = MyRadioButtonList.SelectedValue;

PlaceHolderControl.Controls.Add(LoadControl(MyRadioButtonList.SelectedValue)
);
}

Any suggestions would be much appreciated.
- Harry
 
J

Jeff Evans

Harry said:
Hello Again,
Quick update, I have added code to my Page_Load event such that it will
only load the control named in the ViewState if the PostBack was NOT
raised by the RadioButtonList. The updated code looks as follows:
if (ViewState["ControlName"] != null &&
Request["__EVENTTARGET"] != ModeRadioButtonList.ClientID + "_0" &&
Request["__EVENTTARGET"] != ModeRadioButtonList.ClientID + "_1" &&
Request["__EVENTTARGET"] != ModeRadioButtonList.ClientID + "_2")
This corrects the problem, however, an exception is thrown when switching
user controls to load by clicking various radio buttons in the list. The
exception reads as follows:
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.
Looks like my updated solution is not acceptable afterall. This is puzling
since the ASP.NET exception indicates the control must be reloaded during
the PostBack, however, I did this the first time around and then removed
it when the SelectedIndexChanged event occured and replaced it with the
requested UserControl, but that solution failed as well since the newly
added UserConrol did not handle PostBacks correctly (see my original
code). - Harry
Harry said:
Hello,
I have a page with a RadioButtonList and a PlaceHolder control. The
RadioButtonList's AutoPostBack attribute is set to TRUE and its
SelectedIndexChanged event loads one of three UserControls into the
PlaceHolder's child control collection depending upon which of the three
radio buttons is selected. Each of the three UserControls have postback
events themselves triggered by button clicks. The problem I'm having is
keeping track of what UserControl to load upon a page being posted back. My
SelectedIndexChanged event will load a UserControl dynamically just fine,
however, in the event that the dynamically loaded user control itself issues
a postback the parnets Page_Load event must remeber which UserControl to
reload so it can handle its postback. In order to do this, I store the
loaded control's name in the ViewState. This solution causes a problem in
that moveing from one dynamically loaded UserControl to another will
cause two controls to be displayed at the same time. To fix this my
SelectedIndexChanged event will remove the first child control in the
PlaceHolder. However, this solution totally screws with my postback such
that they do not fire porperly. Here is an example of what I'm doing:
private void Page_Load(object sender, System.EventArgs e)
{
if (ViewState["ControlName"] != null) PlaceHolderControl.Controls.Add(LoadControl(ViewState["ControlName"].ToStr
in
g()));
}
private void MyRadioButtonList_SelectedIndexChanged(object sender,
System.EventArgs e)
{
if (PlaceHolderControl.HasControls())
PlaceHolderControl.Controls.RemoveAt(0);
ViewState["ControlName"] = MyRadioButtonList.SelectedValue; PlaceHolderControl.Controls.Add(LoadControl(MyRadioButtonList.SelectedValu
e)
);


I have the least hassle with dynamic controls when I have references to each
possible Control explicity, then just create a new object for that reference
and add it to the placeholder as needed. For instance:


Control uc1;
Control uc2;

if (someCondition)
{
uc1 = LoadControl("UserControl1");
uc1.ID = "uc1";
placeHolder.Controls.Add(uc1);
}
else
{
//load uc2 in same manner
}


At any rate, if I had to guess I'd say the reason Viewstate is getting
confused has to do with naming conflicts. Unless you explicitly set the ID,
it will usually get something like "_ctl4" or something, where "4" is
however many auto-named controls there have been so far. What might be
happening then is:

1) On first request, you load a UserControl of type "UC1" to the
placeholder, ID is not explicitly set, so it is "_ctl1"
2) You make a postback
3) On this request, you want to switch to a different control, "UC2" so you
load it, it gets same ID "_ctl1" and ViewState goes to try to load - blows
up because it's a different control than "_ctl1" was before

In short, I would try either setting the ID explicity within each user
control you have to something unique or setting it when you create it in the
parent.
 

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,994
Messages
2,570,223
Members
46,814
Latest member
SpicetreeDigital

Latest Threads

Top