上节概述:
上节中主要总结了事件处理的机制、控件事件回发、服务端回发事件的捕获及处理和异步回调等几个方面。
接下来本文将为大家描述“数据回发”的相关细节。
1.IPostBackDataHandler接口
我们封装的控件如果要实现数据回发功能的话,就必须继承IPostBackDataHandler接口并实现接口方法。该接口方法只有两个:
publicbool LoadPostData(string postDataKey,
{
thrownew NotImplementedException();
}
publicvoid RaisePostDataChangedEvent()
{
thrownew NotImplementedException();
}
#endregion
其中,当客户端触发回发事件时,控件首先处理LoadPostData()方法将页面两次新旧值进行对比分析,返回true或false。如果新旧值不等返回true,相等则返回false。当然返回true或false是用控件开发人员自己决定的。但LoadPostData()方法返回的true和false是与后文有直接关系的——返回true时,ASP.NET框架将自动调用RaisePostDataChangeEvent()方法进行必要事件处理;返回false则不会调用RaisePostDataChangeEvent()方法。
上文中提到了控件的新旧值,那么旧值存储在哪里?新值又怎样的被传入呢?分析下LoadPostData()方法的两个参数,其中第一个是String类型的,第二个是NameValueCollection类型的;可知第二个参数是一个集合类型的参数,所以上文提到的新值或者旧值很有可能包含在其中。
为了检验我们的猜疑,下面还是以一个实例来探讨下。
(1).新建一个服务器控件开发工程(命名为PostDataControl)和一个测试项目。
(如果有不清楚的请参照上节——ASP.NET服务器控件封装-【事件】-1.1【事件回发,异步回调】)。
其中包含了一个默认的Text属性和Render()方法,还有IPostBackDataHandler接口的两个空方法体。
(2).上图中我们可以看到Text属性的get方法中使用了ViewState对象,该对象是Control基类中的一个属性,用来管理控件的视图状态的(后面的文章中将讲解视图相关处理),而前面说过,数据回发是在视图状态基础之上实现的,该处就可以说明这一点。
(3).值改变才能触发相关事件的控件,大家很容易就想到TextBox,这里也不例外,在Render()方法中输入一下代码:
{
String PostBackJs = Page.ClientScript.GetPostBackEventReference(this, "");
StringBuilder outString =new StringBuilder();
outString.Append("<Input type='text' name='");
outString.Append(this.UniqueID);
outString.Append("' value=\"");
outString.Append(HttpUtility.HtmlEncode(Text));
outString.Append("\" onblur=\"");
outString.Append(PostBackJs);
outString.Append(";\"></Input>");
output.Write(outString.ToString());
}
主要输出一个文本框,另外我们定义了当焦点从该文本框离开时触发回发事件的事件函数Onblur()。还有一点,我将该文本框的Text值通过HtmlEncode()方法处理生成不包含Html标签的字符串后呈现个客户端。
(4).接下来给该文本框定义一个TextChanged委托和OnTextChanged方法,用来关联用户OnTextChanged事件函数。并且将OnTextChanged事件设置为该控件类PostDataControl的默认属性:
[DefaultEvent("TextChanged")]
[ToolboxData("<{0}:PostDataControl runat=server></{0}:PostDataControl>")]
publicclass PostDataControl : Control,IPostBackDataHandler
{
...
publicevent EventHandler TextChanged;
protectedvirtualvoid OnTextChanged(EventArgs e)
{
if (TextChanged !=null)
{
TextChanged(this, e);
}
}
...
}
(5).重新生成解决方案,从工具箱中拖拽一个PostDataControl控件,到测试项目中的Default.aspx页面内form下的div标签对中。回到控件工程在IPostBackDataHandler接口的两个方法中都打下断点,运行程序。
(6).将焦点出入PostDataControl1文本框输入“123”,然后将焦点移除,程序会在LoadPostData方法内的断点处停下,将鼠标移到该方法的两个参数上,查看两个参数包含的值。
可见,postDataKey的值就是该控件的服务端ID,而postCollection中则包含了视图状态的所有数据(当然,该控件的ID也在其中),如下图。
(7).现在在LoadPostData()方法中输入下面代码,检验那个是旧值那个是新值。
2 string strValue2 = postCollection[this.UniqueID];
全工程重新生成,从工具箱中重新拖拽一个该控件替换掉原有的该控件。运行程序检验后可知Text中保存的是原有值,而postCollection集合中的值才是新值。
其实不难想象,Text中的值是在页面初始进入时视图状态中的值,这时该值还为空,当我们在控件中输入“123”后移出焦点,程序执行到LoadPostData()方法时Text还为空,而新的值则通过postCollection带入到LoadPostData()方法,当程序处理完逻辑后会将新的数据覆盖到视图状态字段中,这时Text的值就是“123”了,如果再将该文本框的值改为“456”后移出焦点,之时在LoadPostData中Text的值就是“123”了。
(8).接下来实现IPostBackDataHandler接口的两个方法,代码如下:
{
string strOldValue = Text;
string strNewValue = postCollection[this.UniqueID];
if (strOldValue !=null&& (strNewValue !=null&&!strOldValue.Equals(strNewValue)))
{
Text = strNewValue;
returntrue;
}
returnfalse;
//throw new NotImplementedException();
publicvoid RaisePostDataChangedEvent()
{
//throw new NotImplementedException();
OnTextChanged(EventArgs.Empty);
}
并在测试项目的Default.aspx中的PostDataControl1控件上双击,在系统自动生成的OnTextChanged事件函数内打上断点,看看如何处理。
(9).同样重新生成后将新的控件替换就的控件后运行程序。发现当文本框中的数据改变后移出焦点,会进入PostDataControl1_OnTextChanged()事件函数中,开发人员可以在该函数中处理相关逻辑了。
如上,将数据回传讲解的很细致了,当然,该控件还之能在焦点移出后触发OnTextChanged事件,还不能像微软的TextBox控件那样文本改变后立即触发OnTextChanged事件,有兴趣的不妨尝试下,有挑战才有乐趣嘛...
控件的完整代码还是附上:
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace PostBackEventControlDome
{
[DefaultProperty("Text")]
[DefaultEvent("TextChanged")]
[ToolboxData("<{0}:PostDataControl runat=server></{0}:PostDataControl>")]
publicclass PostDataControl : Control,IPostBackDataHandler
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
publicstring Text
{
get
{
String s = (String)ViewState["Text"];
return ((s ==null) ? String.Empty : s);
}
set
{
ViewState["Text"] = value;
}
}
protectedoverridevoid Render(HtmlTextWriter output)
{
String PostBackJs = Page.ClientScript.GetPostBackEventReference(this, "");
StringBuilder outString =new StringBuilder();
outString.Append("<Input type='text' name='");
outString.Append(this.UniqueID);
outString.Append("' value=\"");
outString.Append(HttpUtility.HtmlEncode(Text));
outString.Append("\" onblur=\"");
outString.Append(PostBackJs);
outString.Append(";\"></Input>");
output.Write(outString.ToString());
}
publicevent EventHandler TextChanged;
protectedvirtualvoid OnTextChanged(EventArgs e)
{
if (TextChanged !=null)
{
TextChanged(this, e);
}
}
#region IPostBackDataHandler 成员
publicbool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
string strOldValue = Text;
string strNewValue = postCollection[this.UniqueID];
if (strOldValue !=null&&
(strNewValue !=null&&!strOldValue.Equals(strNewValue)))
{
Text = strNewValue;
returntrue;
}
returnfalse;
//throw new NotImplementedException();
}
publicvoid RaisePostDataChangedEvent()
{
//throw new NotImplementedException();
OnTextChanged(EventArgs.Empty);
}
#endregion
}
}
最后:
回发的总结就做到这里了。
其实ASP.NET为事件还提供了高效的事件集合。可以想象,如果在复合的控件中事件比较多的情况下还是采用一个委托一个事件实现对象的方式的话,将会耗费相当的内存控件。
另外,由于学习控件封装的时间还不长,在理解上面可能存在偏差,还希望各路大侠们给予指点,在下不甚感激啊!
接下来的文章将对视图状态和控件状态进行总结了。