Asp.net TextBox的TextChanged事件使用介绍
在博客园里有位兄弟问了我一个问题,动态创建的控件是如何加载视图状态,还提到processpostdata方法的调用。这里我就用textbox的textchanged事件来说说视图数据的加载以及事件的触发。
我们首先还是来看一个demo吧:
代码如下:
运行结果如图:
现在我们来修改文本框的值,然后点击按钮提交页面,看看有什么事情发生
textbox的textchanged事件这时候发生了,运行结果如图:
现在我们什么也不做,再次点击按钮提交,看看会有什么效果:
这是为什么了,textbox的textchanged事件这时候没有触发。
大家是否知道textbox的textchanged事件触发的条件了,那好我们今天就一起来看看该事件是如何触发的。
这里我们首先来看看textbox的定义:
public class textbox : webcontrol, ipostbackdatahandler, ieditabletextcontrol, itextcontrol
public interface ipostbackdatahandler
{
bool loadpostdata(string postdatakey, namevaluecollection postcollection);
void raisepostdatachangedevent();
}
public interface ieditabletextcontrol : itextcontrol
{
event eventhandler textchanged;
}
public interface itextcontrol
{
string text { get; set; }
}
这里我们最主要的是关注ipostbackdatahandler接口的实现,
protected virtual bool loadpostdata(string postdatakey, namevaluecollection postcollection)
{
base.validateevent(postdatakey);
string text = this.text;
string str2 = postcollection[postdatakey];
if (!this.readonly && !text.equals(str2, stringcomparison.ordinal))
{
this.text = str2;
return true;
}
return false;
}
protected virtual void raisepostdatachangedevent()
{
if (this.autopostback && !this.page.ispostbackeventcontrolregistered)
{
this.page.autopostbackcontrol = this;
if (this.causesvalidation)
{
this.page.validate(this.validationgroup);
}
}
this.ontextchanged(eventargs.empty);
}
这里的raisepostdatachangedevent方法比较好理解,主要就是调用textchanged事件方法,而loadpostdata方法中是可以取到textbox当前值(旧值 string text = this.text;)和post过来的新值( postcollection[postdatakey]),如果当前textbox不是只读,并且新旧值不等的话,则吧新值赋给textbox的text属性,返回true,否者返回false,这里我们能否猜测textbox的loadpostdata返回true,我们才调用raisepostdatachangedevent方法。
在前面的asp.net page事件处理管道我们曾经提到两段比较特殊的代码,一段是处理ipostbackdatahandler一段是处理ipostbackeventhandler。
首先我们还是来先看看
this.oninitcomplete(eventargs.empty);
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end initcomplete");
}
if (this.ispostback)
{
if (context.traceisenabled)
{
this.trace.write("aspx.page", "begin loadstate");
}
this.loadallstate();
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end loadstate");
this.trace.write("aspx.page", "begin processpostdata");
}
this.processpostdata(this._requestvaluecollection, true);
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end processpostdata");
}
}
if (context.traceisenabled)
{
this.trace.write("aspx.page", "begin preload");
}
this.onpreload(eventargs.empty);
这一段吧,在initcomplete之后、preload之前我们这里在处理ipostbackdatahandler接口,这里主要是一个loadallstate和processpostdata方法。
首先我们需要知道这里的_requestvaluecollection是一个什么东西,其实很简单,如果是post主要是this._request.form(其中有些过滤处理,如过滤掉__viewstate",__eventtarget),如果是get请求有querystring集合则是this._request.querystring就是this._request.querystring。
loadallstate的主要带代码如下:
view code
?private void loadallstate()
{
object obj2 = this.loadpagestatefrompersistencemedium();
idictionary first = null;
pair second = null;
pair pair2 = obj2 as pair;
if (obj2 != null)
{
first = pair2.first as idictionary;
second = pair2.second as pair;
}
if (first != null)
{
this._controlsrequiringpostback = (arraylist) first["__controlsrequirepostbackkey__"];
if (this._registeredcontrolsrequiringcontrolstate != null)
{
foreach (control control in (ienumerable) this._registeredcontrolsrequiringcontrolstate)
{
control.loadcontrolstateinternal(first[control.uniqueid]);
}
}
}
if (second != null)
{
string s = (string) second.first;
int num = int.parse(s, numberformatinfo.invariantinfo);
this._fpagelayoutchanged = num != this.gettypehashcode();
if (!this._fpagelayoutchanged)
{
base.loadviewstaterecursive(second.second);
}
}
}
protected internal virtual object loadpagestatefrompersistencemedium()
{
pagestatepersister pagestatepersister = this.pagestatepersister;
try
{
pagestatepersister.load();
}
catch (httpexception exception)
{
if (this._pageflags[8])
{
return null;
}
exception.webeventcode = 0xbba;
throw;
}
return new pair(pagestatepersister.controlstate, pagestatepersister.viewstate);
}
internal void loadchildviewstatebyid(arraylist childstate)
{
int count = childstate.count;
for (int i = 0; i < count; i += 2)
{
string id = (string) childstate[i];
object savedstate = childstate[i + 1];
control control = this.findcontrol(id);
if (control != null)
{
control.loadviewstaterecursive(savedstate);
}
else
{
this.ensureoccasionalfields();
if (this._occasionalfields.controlsviewstate == null)
{
this._occasionalfields.controlsviewstate = new hashtable();
}
this._occasionalfields.controlsviewstate[id] = savedstate;
}
}
}
loadallstate方法注意到是加载每个控件的controlstate和viewstate数据,数据来源是通过loadpagestatefrompersistencemedium方法获得的,数据类容就是上次response中各控件的controlstate数据和viewstate数据。
接下来我们该看看processpostdata方法,
private void processpostdata(namevaluecollection postdata, bool fbeforeload)
{
if (this._changedpostdataconsumers == null)
{
this._changedpostdataconsumers = new arraylist();
}
if (postdata != null)
{
foreach (string str in postdata)
{
if ((str != null) && !issystempostfield(str))
{
control control = this.findcontrol(str);
if (control == null)
{
if (fbeforeload)
{
if (this._leftoverpostdata == null)
{
this._leftoverpostdata = new namevaluecollection();
}
this._leftoverpostdata.add(str, null);
}
}
else
{
ipostbackdatahandler postbackdatahandler = control.postbackdatahandler;
if (postbackdatahandler == null)
{
if (control.postbackeventhandler != null)
{
this.registerrequiresraiseevent(control.postbackeventhandler);
}
}
else
{
if (postbackdatahandler != null)
{
namevaluecollection postcollection = control.calculateeffectivevalidaterequest() ? this._requestvaluecollection : this._unvalidatedrequestvaluecollection;
if (postbackdatahandler.loadpostdata(str, postcollection))
{
this._changedpostdataconsumers.add(control);
}
}
if (this._controlsrequiringpostback != null)
{
this._controlsrequiringpostback.remove(str);
}
}
}
}
}
}
arraylist list = null;
if (this._controlsrequiringpostback != null)
{
foreach (string str2 in this._controlsrequiringpostback)
{
control control2 = this.findcontrol(str2);
if (control2 != null)
{
ipostbackdatahandler adapterinternal = control2.adapterinternal as ipostbackdatahandler;
if (adapterinternal == null)
{
adapterinternal = control2 as ipostbackdatahandler;
}
if (adapterinternal == null)
{
throw new httpexception(sr.getstring("postback_ctrl_not_found", new object[] { str2 }));
}
namevaluecollection values2 = control2.calculateeffectivevalidaterequest() ? this._requestvaluecollection : this._unvalidatedrequestvaluecollection;
if (adapterinternal.loadpostdata(str2, values2))
{
this._changedpostdataconsumers.add(control2);
}
}
else if (fbeforeload)
{
if (list == null)
{
list = new arraylist();
}
list.add(str2);
}
}
this._controlsrequiringpostback = list;
}
}
首先根据创建来的参数namevaluecollection的key来查找我们的control控件,一般情况下控件是可以找到的,但是在load中动 态创建的控件这里是找不到的。这个方法分为两部分,以 arraylist list = null;这句代码分开,一部分如果中不到control控件处理比较简单,如果找到看看它是不是postbackdatahandler类型,如果不是 并且它的postbackeventhandler不为空,那么我们直接调用它 的 this.registerrequiresraiseevent(control.postbackeventhandler)方法,如果是 postbackeventhandler类型的控件我们直接调用它的loadpostdata方法,
if (postbackdatahandler.loadpostdata(str, postcollection))
{
this._changedpostdataconsumers.add(control);
}
同时从_controlsrequiringpostback集合中移除该控件
if (this._controlsrequiringpostback != null)
{
this._controlsrequiringpostback.remove(str);
}
该方法的第二部分是遍历controlsrequiringpostback中的集合,它的处理方式和上面一部分类似,只是没有找到控件的id则记录下来
else if (fbeforeload)
{
if (list == null)
{
list = new arraylist();
}
list.add(str2);
}
默认情况下_controlsrequiringpostback是包含动态创建的控件。这里我们也说说这个集合吧,
controlsrequiringpostback的设置是在loadallstate方法中的这一句代码:
this._controlsrequiringpostback = (arraylist) first["__controlsrequirepostbackkey__"];有loadallstate(加载数据状态)就有saveallstate(保存数据状态),在saveallstate中有这么一句代码:
dictionary.add("__controlsrequirepostbackkey__", this._registeredcontrolsthatrequirepostback);
其中_registeredcontrolsthatrequirepostback集合定义在registerrequirespostback方法中。
public void registerrequirespostback(control control)
{
if (!(control is ipostbackdatahandler) && !(control.adapterinternal is ipostbackdatahandler))
{
throw new httpexception(sr.getstring("ctrl_not_data_handler"));
}
if (this._registeredcontrolsthatrequirepostback == null)
{
this._registeredcontrolsthatrequirepostback = new arraylist();
}
this._registeredcontrolsthatrequirepostback.add(control.uniqueid);
}
总之在这里动态添加的控件是没办法加载数据的,但是其它默认的控件在这里都可以处理。
现在我们来看看控件是如何添加的,在control类中有一个addedcontrol方法是真正添加控件的处理:
protected internal virtual void addedcontrol(control control, int index)
{
if (control.ownercontrol != null)
{
throw new invalidoperationexception(sr.getstring("substitution_notallowed"));
}
if (control._parent != null)
{
control._parent.controls.remove(control);
}
control._parent = this;
control._page = this.page;
control.flags.clear(0x20000);
control namingcontainer = this.flags[0x80] ? this : this._namingcontainer;
if (namingcontainer != null)
{
control.updatenamingcontainer(namingcontainer);
if ((control._id == null) && !control.flags[0x40])
{
control.generateautomaticid();
}
else if ((control._id != null) || (control._controls != null))
{
namingcontainer.dirtynametable();
}
}
if (this._controlstate >= controlstate.childreninitialized)
{
control.initrecursive(namingcontainer);
if (((control._controlstate >= controlstate.initialized) && (control.rarefields != null)) && control.rarefields.requiredcontrolstate)
{
this.page.registerrequirescontrolstate(control);
}
if (this._controlstate >= controlstate.viewstateloaded)
{
object savedstate = null;
if ((this._occasionalfields != null) && (this._occasionalfields.controlsviewstate != null))
{
savedstate = this._occasionalfields.controlsviewstate[index];
if (this.loadviewstatebyid)
{
control.ensureid();
savedstate = this._occasionalfields.controlsviewstate[control.id];
this._occasionalfields.controlsviewstate.remove(control.id);
}
else
{
savedstate = this._occasionalfields.controlsviewstate[index];
this._occasionalfields.controlsviewstate.remove(index);
}
}
control.loadviewstaterecursive(savedstate);
if (this._controlstate >= controlstate.loaded)
{
control.loadrecursive();
if (this._controlstate >= controlstate.prerendered)
{
control.prerenderrecursiveinternal();
}
}
}
}
}
在这个方法中有调用 this.page.registerrequirescontrolstate(control) 和 control.loadviewstaterecursive(savedstate)方法,一个负责controlstate一个负责 viewstate的数据加载,当我们这里第2次和3次post请求时,在load创建textboxt控件就会加载它已有的控件状态和视图状态。
现在我们再来看看processrequestmain中处理ipostbackeventhandler的那段带代码:
this.loadrecursive();
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end load");
}
if (this.ispostback)
{
if (context.traceisenabled)
{
this.trace.write("aspx.page", "begin processpostdata second try");
}
this.processpostdata(this._leftoverpostdata, false);
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end processpostdata second try");
this.trace.write("aspx.page", "begin raise changedevents");
}
this.raisechangedevents();
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end raise changedevents");
this.trace.write("aspx.page", "begin raise postbackevent");
}
this.raisepostbackevent(this._requestvaluecollection);
if (context.traceisenabled)
{
this.trace.write("aspx.page", "end raise postbackevent");
}
}
if (context.traceisenabled)
{
this.trace.write("aspx.page", "begin loadcomplete");
}
this.onloadcomplete(eventargs.empty);
首先我们来看看_leftoverpostdata集合是什么,它是在先前一次调用processpostdata方法时没有找到控件的一个id集合。在这里就可以找到该控件,执行路线主要就是 arraylist list = null这句后面部分,最终还是要调用
if (adapterinternal.loadpostdata(str2, values2))
{
this._changedpostdataconsumers.add(control2);
}
这个方法,这里第二次调用processpostdata方法主要就就是处理动态创建控件的事件问题。
这里我们再来看看raisechangedevents方法吧:
internal void raisechangedevents()
{
if (this._changedpostdataconsumers != null)
{
for (int i = 0; i < this._changedpostdataconsumers.count; i++)
{
control control = (control) this._changedpostdataconsumers[i];
if (control != null)
{
ipostbackdatahandler postbackdatahandler = control.postbackdatahandler;
if (((control == null) || control.isdescendentof(this)) && ((control != null) && (control.postbackdatahandler != null)))
{
postbackdatahandler.raisepostdatachangedevent();
}
}
}
}
}
我想到这里textbox的textchanged事件的执行你应该很清楚了吧。而raisepostbackevent方法就不说,看代码大家都会明白的,
private void raisepostbackevent(namevaluecollection postdata)
{
if (this._registeredcontrolthatrequireraiseevent != null)
{
this.raisepostbackevent(this._registeredcontrolthatrequireraiseevent, null);
}
else
{
string str = postdata["__eventtarget"];
bool flag = !string.isnullorempty(str);
if (flag || (this.autopostbackcontrol != null))
{
control control = null;
if (flag)
{
control = this.findcontrol(str);
}
if ((control != null) && (control.postbackeventhandler != null))
{
string eventargument = postdata["__eventargument"];
this.raisepostbackevent(control.postbackeventhandler, eventargument);
}
}
else
{
this.validate();
}
}
}
到这里我们在回忆一下,一般控件的状态信息保存是通过saveallstate方法,而加载状态信息是在initcomplete之后、preload之前的loadallstate方法,加载的数据就是上次请求saveallstate方 法保存的数据,加载状态后调用processpostdata方法来处理post过来的数据,动态添加的控件在第二次及后面每次请求添加时都会加载状态数 据,说直接一点是动态添加的控件在添加的时候就加载它的状态数据。在 load之后、 loadcomplete之前就是我们处理控件的事件调用问题,这里我们再次调用processpostdata用来处理动态创建的控件与post过来的 数据,之后分别调用raisechangedevents、raisepostbackevent方法拉起处理 ipostbackdatahandler、ipostbackeventhandler中的事件调用。