问题
I have page with a LinkButton on it that when clicked, I'd like to add a Usercontrol to the page. I need to be able to add/remove as many controls as the user would like. The Usercontrol consists of three dropdownlists. The first dropdownlist has it's auotpostback property set to true and hooks up the OnSelectedIndexChanged event that when fired will load the remaining two dropdownlists with the appropriate values.
My problem is that no matter where I put the code in the host page, the usercontrol is not being loaded properly. I know I have to recreate the usercontrols on every postback and I've created a method that is being executed in the hosting pages OnPreInit method. I'm still getting the following error: The control collection cannot be modified during DataBind, Init, Load, PreRender or Unload phases.
Here is my code: Thank you!!!!
bool createAgain = false;
IList<FilterOptionsCollectionView> OptionControls
{
get
{
if (SessionManager.Current["controls"] != null)
return (IList<FilterOptionsCollectionView>)SessionManager.Current["controls"];
else
SessionManager.Current["controls"] = new List<FilterOptionsCollectionView>();
return (IList<FilterOptionsCollectionView>)SessionManager.Current["controls"];
}
set
{
SessionManager.Current["controls"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
Master.Page.Title = Title;
LoadViewControls(Master.MainContent, Master.SideBar, Master.ToolBarContainer);
}
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
System.Web.UI.MasterPage m = Master;
Control control = GetPostBackControl(this);
if ((control != null && control.ClientID ==
(lbAddAndCondtion.ClientID) || createAgain))
{
createAgain = true;
CreateUserControl(control.ID);
}
}
protected void AddAndConditionClicked(object o, EventArgs e)
{
var control = LoadControl("~/Views/FilterOptionsCollectionView.ascx");
OptionControls.Add((FilterOptionsCollectionView)control);
control.ID = "options" + OptionControls.Count.ToString();
phConditions.Controls.Add(control);
}
public event EventHandler<Insight.Presenters.PageViewArg> OnLoadData;
private Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
{
return root;
}
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
protected Control GetPostBackControl(System.Web.UI.Page page)
{
Control control = null;
string ctrlname = Page.Request.Params["__EVENTTARGET"];
if (ctrlname != null && ctrlname != String.Empty)
{
control = FindControlRecursive(page, ctrlname.Split('$')[2]);
}
else
{
string ctrlStr = String.Empty;
Control c = null;
foreach (string ctl in Page.Request.Form)
{
if (ctl.EndsWith(".x") || ctl.EndsWith(".y"))
{
ctrlStr = ctl.Substring(0, ctl.Length - 2);
c = page.FindControl(ctrlStr);
}
else
{
c = page.FindControl(ctl);
}
if (c is System.Web.UI.WebControls.CheckBox ||
c is System.Web.UI.WebControls.CheckBoxList)
{
control = c;
break;
}
}
}
return control;
}
protected void CreateUserControl(string controlID)
{
try
{
if (createAgain && phConditions != null)
{
if (OptionControls.Count > 0)
{
phConditions.Controls.Clear();
foreach (var c in OptionControls)
{
phConditions.Controls.Add(c);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
Here is the usercontrol's code:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="FilterOptionsCollectionView.ascx.cs" Inherits="Insight.Website.Views.FilterOptionsCollectionView" %>
namespace Insight.Website.Views
{ [ViewStateModeById] public partial class FilterOptionsCollectionView : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) {
}
protected override void OnInit(EventArgs e)
{
LoadColumns();
ddlColumns.SelectedIndexChanged += new RadComboBoxSelectedIndexChangedEventHandler(ColumnsSelectedIndexChanged);
base.OnInit(e);
}
protected void ColumnsSelectedIndexChanged(object o, EventArgs e)
{
LoadCriteria();
}
public void LoadColumns()
{
ddlColumns.DataSource = User.GetItemSearchProperties();
ddlColumns.DataTextField = "SearchColumn";
ddlColumns.DataValueField = "CriteriaSearchControlType";
ddlColumns.DataBind();
LoadCriteria();
}
private void LoadCriteria()
{
var controlType = User.GetItemSearchProperties()[ddlColumns.SelectedIndex].CriteriaSearchControlType;
var ops = User.GetItemSearchProperties()[ddlColumns.SelectedIndex].ValidOperators;
ddlOperators.DataSource = ops;
ddlOperators.DataTextField = "key";
ddlOperators.DataValueField = "value";
ddlOperators.DataBind();
switch (controlType)
{
case ResourceStrings.ViewFilter_ControlTypes_DDL:
criteriaDDL.Visible = true;
criteriaText.Visible = false;
var crit = User.GetItemSearchProperties()[ddlColumns.SelectedIndex].SearchCriteria;
ddlCriteria.DataSource = crit;
ddlCriteria.DataBind();
break;
case ResourceStrings.ViewFilter_ControlTypes_Text:
criteriaDDL.Visible = false;
criteriaText.Visible = true;
break;
}
}
public event EventHandler OnColumnChanged;
public ISearchCriterion FilterOptionsValues { get; set; }
}
}
回答1:
I figured it out. Here is my solution:
I modified the GetPostBackControl to look for not only the linkbutton that inserts the user control, but for controls that contain the id of child controls of the inserted user control(as to capture the OnSelectedIndexChanged that gets fired from inside my user control).
protected Control GetPostBackControl(System.Web.UI.Page page)
{
Control control = null;
string ctrlname = Page.Request.Params["__EVENTTARGET"];
if (ctrlname != null && ctrlname != String.Empty)
{
//if it contains options then it's a control inside my usercontrol
if (ctrlname.Split('$')[2].Contains("options"))
{
var c = new Control();
c.ID = ctrlname;
return c;
}
else
{
control = FindControlRecursive(page, ctrlname.Split('$')[2]);
}
}
else
{
string ctrlStr = String.Empty;
Control c = null;
foreach (string ctl in Page.Request.Form)
{
if (ctl.EndsWith(".x") || ctl.EndsWith(".y"))
{
ctrlStr = ctl.Substring(0, ctl.Length - 2);
c = page.FindControl(ctrlStr);
}
else
{
c = page.FindControl(ctl);
}
if (c is System.Web.UI.WebControls.CheckBox ||
c is System.Web.UI.WebControls.CheckBoxList)
{
control = c;
break;
}
}
}
return control;
}
Then I modify the OnPreInit event to look for controls with an id of the linkbutton or an id that contains "options" :
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
System.Web.UI.MasterPage m = Master;
Control control = GetPostBackControl(this);
if (control != null)
{
if ((control.ClientID == (lbAddAndCondtion.ClientID) || createAgain) || control.ID.Contains("options"))
{
createAgain = true;
CreateUserControl(control.ID);
}
}
}
The critical fix was in the CreateUserControl method. In my original code I was trying to directly load the user control from my generic list that was stored in Session. I changed that to actually create a new instance of the user control, assign that new instance an id that matches the one stored in Session, and then add it to the placeholder:
protected void CreateUserControl(string controlID)
{
try
{
if (createAgain && phConditions != null)
{
if (OptionControls.Count > 0)
{
phConditions.Controls.Clear();
foreach (var c in OptionControls)
{
FilterOptionsCollectionView foc = new FilterOptionsCollectionView();
foc = Page.LoadControl("~/Views/FilterOptionsCollectionView.ascx") as FilterOptionsCollectionView;
foc.ID = c.ID;
phConditions.Controls.Add(foc);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
The only thing I changed in the user control was moving the method that loads my drop down lists's and wiring up the OnSelectedIndexChanged event into the OnInit event. Now I can dynamically load as many instances of the user control I want and all of the event's inside the user control fire correctly and state is persisted across postbacks!!
Hope this helps someone else!!
来源:https://stackoverflow.com/questions/4671106/saving-state-dynamic-usercontrols-help