如果你看了这篇文章而没有看之前的文章,那还是建议你看一看;因为光看这一篇文章,你很难明白我到底想说些啥;
前面说到,对于我们的例子页面,可能大家都忘记了例子页面了;
<div><asp:Label ID="Label1" runat="server" Text="Label"></asp:Label></div>
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate>
<asp:CheckBox ID="HeadCheckBox" runat="server" Text="Head"/>
</HeaderTemplate>
</asp:Repeater>
protected void Page_Load(object sender, EventArgs e)
{
this.Repeater1.DataSource = new object[] { };
this.Repeater1.DataBind();
var CheckBox4 = new CheckBox();
CheckBox4.ID = "DynamicCheckBox";
CheckBox4.Text = CheckBox4.ID;
CheckBox4.AutoPostBack = true;
CheckBox4.CheckedChanged += ck_CheckedChanged;
this.form1.Controls.Add(CheckBox4);
CheckBox ck = (CheckBox) Repeater1.Controls[0].Controls[1];
ck.AutoPostBack = true;
ck.CheckedChanged += ck_CheckedChanged;
}
protected void ck_CheckedChanged(object sender, EventArgs e)
{
CheckBox cb = (CheckBox)sender;
var lbText = cb.ClientID + (cb.Checked ? " checked" : " not checked") + ":"+cb.Text;
this.Label1.Text = lbText;
}
页面很简单,问题也很简单,Repeater中Header行的CheckBox改变状态时不会触发 ck_CheckedChanged 方法;
而动态创建的CheckBox改变状态时则会触发,why?
在Page类的ProcessReques调用 OnPreLoad 之前,Repeater中根据PostBack回来的ViewState中的数据,已经重新构建了提交前页面中的Child Controls;
并且,Page类的一个私有变量 _changedPostDataConsumers 里面已经记录了Repeater控件Header行这个CheckBox控件;
理论上,不出意外的话,在 Page_Load之后,会触发事件的;
现在先看看 OnPreLoad 函数, OnPreLoad处理相当简单,看看当前Page类注册了哪些PreLoad事件,然后一个个调用
有的子控件会在OnInit中给Page的PreLoad增加自己的PreLoad事件处理;例如 Repeater控件;
总之,子控件增加的PreLoad事件应该在父控件的PreLoad事件之前触发;
OnPreLoad之后,调用LoadRecursive 函数,也就是 OnLoad了;
和PreLoad的处理顺序正好相反,父控件的Load事件将在子控件的Load事件之后触发;
LoadRecursive 这个函数就是递归调用本控件的OnLoad函数以及所有子控件的LoadRecursive 函数;
页面你所写的Page_Load函数也就在这个时候被调用了;
本来以为,在 Page_Load的
this.Repeater1.DataSource = new object[] { };
this.Repeater1.DataBind();
这两句代码执行后,Page类的这个私有变量_changedPostDataConsumers 中就会没有原来LoadViewState方法产生的那个 CheckBox了,结果发现还在;
在整个OnLoad执行完后,依旧还在;
不管,我们继续往下看,看到底啥时候这个创建了又因为Repeaer重新绑定数据源而无声无息被删除了的CheckBox被从_changedPostDataConsumers 中删除;
好,OnLoad也完成了,轮到哪儿了?大家猜猜;估计很多人会猜测错误;
不是 OnLoadComplete ,而先要干另外一点事情;
判断IsPostBack为true,
1。再次以如下方式调用ProcessPostData函数:
this.ProcessPostData(this._leftoverPostData, false)函数;
大家如果看过我上一篇文章而且确实好记性的话(我很佩服你,因为过了两天,我就不记得这个函数的第二个参数表示啥意思了);
第一个参数反而记得,在处理Request提交过来的参数时,如果根据名称找到了控件,则该控件将该名称对应的值利用LoadPosData进行调用;
而如果找不到控件,呵呵,将名称加到 _leftoverPostData 中;这儿的this.ProcessPostData就是干这个事情的了;
所以,动态创建的CheckBox在选中状态改变时,在这次调用时ProcessPostData能够设置选中状态,并在之后的后续处理中触发CheckedChanged事件了;
2。接着上面的 this.ProcessPostData(this._leftoverPostData, false)函数之后,就是 RaiseChangedEvents 方法了;
触发页面上控件的各种事件处理了;但是,呵呵,_changedPostDataConsumers 此时是有那个
因为LoadViewState在Repeater中创建又因为Repeaer重新绑定数据源而无声无息被删除了的CheckBox的呃;
仔细看看 RaiseChangedEvents 方法,你会发现这样一句话:
if (((control == null) || control.IsDescendentOf(this)) && ((control != null) && (control.PostBackDataHandler != null)))
{ postBackDataHandler.RaisePostDataChangedEvent();
}
就是这个判断 control.IsDescendentOf(this) 使得 _changedPostDataConsumers 中存放的CheckBox不会触发任何事件的了;
3。接着RaiseChangedEvents,就是 RaisePostBackEvent 了;
这个函数,会干啥呢?判断 ["__EVENTTARGET"] 对应的控件会否是一个PostBackEventHandler属性不为null的控件,例如
按钮,LinkButon,ImageButton 等等;
由此大家也可以看出,如果有一个 CheckBox,设置了CheckedChanged事件,但没有设置AutoPostBack属性,
还有一个按钮,设置了 OnClick 事件,
则,改变CheckBox 选中状态,点击按钮,将首先触发CheckedChanged事件,再触发 OnClick 事件
然后,就到了页面的 OnLoadComplete 了;触发 LoadComplete 事件;
终于,PreLoad,Load,LoadComplete 也完成了;
至于,Render之类的,我就不多说了;
PreRender事件 父控件将先 PreRender,然后子控件 PreRender,
然后调用页面的 PreRenderComplete 事件;
然后调用页面的 SaveAllState 产生所有控件的ViewState构成的Pair对象数,并序列化成byte[]类型,然后利用Base64编码;
然后触发页面的 然后调用页面的 SaveStateComplete 事件;
呵呵,所以,如果你在 SaveStateComplete 中修改了 Label的Text,你会发现提交回来后修改将不记得你的修改;
再然后,调用页面的 RenderControl 方法,将页面以及所有子控件输出到浏览器去;
到了这儿,我也可以顺便和大家解释一下动态控件的事件触发机制了,
上面的代码中,
this.form1.Controls.Add(CheckBox4); //这一句话干了很多事情;
还记得我们的 LoadViewState中,
Pair的Second的格式吗?
[子控件的序号,子控件的Pair,子控件的序号,子控件的Pair...]
如果子控件的序号大于当前子控件的数量?例如,当前页面的静态控件数量只有1个,
如果你在在Page_Load中动态创建了CheckBox,则这个动态创建的CheckBox将占据2的控件序号,
但在LoadViewState时,在Page中当然找不到序号为2的这个控件(因为此时还没有调用LoadRecursive 函数,
所以[2,序号为2的子控件的Pair] 将放到一个变量 _occasionalFields 中;
当调用LoadRecursive 的时候,会调用页面的 Page_Load方法,
这个方法中
运行 this.form1.Controls.Add(CheckBox4); 时,
根据新增加的这个控件的序号,到 _occasionalFields 去找该序号对应的Pair,
如果找到,则调用该控件的 LoadViewStateRecursive 方法来加载提交前页面产生的ViewState来恢复提交前页面的状态;
在 LoadRecursive 完成后,判断IsPostBack为true,再次以如下方式调用ProcessPostData函数:
this.ProcessPostData(this._leftoverPostData, false)函数;
这次,对于这个动态添加的CheckBox, 将根据Post回来的值设置状态;此时,将登记这个CheckBox的CheckedChanged状态;