上次的第二个问题是,为什么动态生成的DropDownList控件,在PostBack后,在Page_Load里其选择的项没有被设置。
拿TestDyn1.aspx为例,如果你在第一个(静态)下拉框里选择2,在第二个(动态)下拉框里选择3,然后按Click Me按钮,你的输出是这样的
[Page_Load]静态:1
[Page_Load]动态:0
[Button_Click]静态:1
[Button_Click]动态:2
不管你选什么,第二项总是
[Page_Load]动态:0
即,动态下拉框的选项在Page_Load没有被正确设置,但在Button的Click事件里被正确设置了。
大家知道,表单控件(TextBox, CheckBox, DropDownList, ListBox,….) 的输入值或被选状态与ViewState无关,而是在Load Postback Data阶段被设置的,因为它们都实现了IPostBackDataHandler接口。
上次说到动态控件被加入父控件的Controls集合时,会通过阶段“追赶(catch-up)”过程来赶上父控件当前的阶段,如果你仔细看一下前一个贴里leighsword和microhelper贴的Control的AddedControl方法,你将看到
control.InitRecursive(control1);
…
control.LoadViewStateRecursive(obj1);
…
control.LoadRecursive();
…
并没有涉及Load Postback Data。那么这个阶段是什么时候被执行的呢?如果你参考Reflector(也可以参考上一个贴的2个回贴)里System.Web.UI.Page的ProcessRequestMain()方法,在去掉了那些Trace语句后是这样的:
base.InitRecursive(null);
if (this.IsPostBack)
{
this.LoadPageViewState();
//注意,这里是._requestValueCollection
this.ProcessPostData(this._requestValueCollection, true); //第二个参数表明是否是在Load前调用的
}
base.LoadRecursive();
if (this.IsPostBack)
{
//注意,这里是._leftoverPostData,即,尚未被处理的PostData
this.ProcessPostData(this._leftoverPostData, false);
this.RaiseChangedEvents();
this.RaisePostBackEvent(this._requestValueCollection);
}
base.PreRenderRecursiveInternal();
this.SavePageViewState();
base.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));
ProcessPostData会根据Request.Form里每对名字/值,看是否有实现了IPostBackDataHandler接口的对应名字的控件,有的话,就会调用该控件的LoadPostData方法,譬如DropDownList的LoadPostData是这样的
bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)
{
string[] textArray1 = postCollection.GetValues(postDataKey);
if (textArray1 != null)
{
int num1 = this.Items.FindByValueInternal(textArray1[0]);
if (this.SelectedIndex != num1)
{
this.SelectedIndex = num1;
return true;
}
}
return false;
}
从上面可见,ProcessPostData在Load前被执行了一次,在Load后又会被执行一次。看上去有点怪,但这正是系统给你的方便,允许你在Load里动态生成控件,并让那些实现了IPostBackDataHandler接口的控件获取用户输入的值或选择的状态。
在我们当前的情形下,我们的动态控件是在Load里生成的,错过了第一次ProcessPostData,所以在Page_Load里其选项还没有被正确设置,但第二次ProcessPostData让其获取了用户输入的值或选择的状态,所以在Button的Click事件里被正确设置了。
这也意味着,如果我们的表单控件是在Load之后生成的,譬如你的控件是在PreRender事件里生成的,
void Page_PreRender(Object sender, EventArgs e)
{
DropDownList ddlDynamic2 = new DropDownList();
ddlDynamic2.ID = “ddlDynamic2”;
form1.Controls.Add(ddlDynamic2);
if (!IsPostBack)
{
for (int i=1; i <=3; i++)
ddlDynamic2.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
else
Response.Write(“[Page_Load]动态2:” + ddlDynamic2.SelectedIndex + “<BR>”);
}
那么尽管它可以恢复ViewState,但因为它错过了2次ProcessPostData机会,它不可能获取用户输入的值或选择的状态。同时这些控件也不会触发Changed Events 与 Postback Events。 当然,你尽可以使用Request.Form来获取用户输入的值或选择的状态,但这跟我们的讨论无关。
所以,如果你需要产生动态控件,而且需要获取用户设置的输入值或触发Changed Events 与 Postback Events事件的话,最好在Load阶段或之前生成。
关于ASP.NET里Page事件及其次序的细节,请参考MVP Paul Wilson的文章
Page Events: Order and PostBack
也请参考
打赏作者