在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
第5章 庖丁解牛系列—事件和数据回发机制
本章内容 5.1 控件事件和数据回发概述 5.2 事件和数据回发机制的实现 5.3 复合控件的事件处理机制 5.1 控件事件和数据回发概述在讲解实现控件事件和数据回发功能之前,有必要先了解一下相关的基本概念。 5.1.1 事件及其意义要为控件定制事件,先得使用控件的事件。首先以经典的Button控件的OnClick事件为例子,说一下事件的使用模型。 1.注册事件<asp:Button ID="Button1" runat="server"OnClick="Button1_Click"Text="Button"/> u 或在Page_Load中注册: protected void Page_Load(object sender, EventArgs e) { this.Button1.Click +=new EventHandler(Button1_Click); } 2.事件方法体protected void Button1_Click(object sender, EventArgs e) { } 首先从事件要实现的功能角度理解事件。我们把按钮(Button)看做一个对象,把页面(Page)也看做一个对象。 正向理解:假如我们在Page对象中要修改Button的行为,可以直接通过this.Button1的形式直接访问Button对象的属性或方法对Button进行修改,可以理解为Page类能访问到Button代码功能。原因是Button1是Page类内部的一个对象,类当然可以直接访问其内部的对象。 逆向理解:假如我们需要在Button中要访问Page中的代码呢?直接像上面那样通过this.Page的形式是不行的,因为Button是Page类的内部对象,但Page不是Button类的内部对象,从面向对象角度讲,类(Button)不能访问其外部的对象(Page),也就是说在Button中不可能通过this.Page的形式访问到Page对象。而使用事件机制就可以解决此问题,即事件机制解决了面向对象编程中不允许类访问类外部代码问题。关于事件的应用本章后面会专门讲解,在这里仅介绍一下它的功能。 更深层了解一下Button的事件:假如我们没有对Button注册Click事件,则Button会执行一遍它内部的Click相关逻辑,并没有对Page对象产生任何影响;如果我们为Button定义了Click事件(如上面代码片段),则Button还是执行一遍它内部的Click相关逻辑,不同的是在执行自己内部逻辑的过程中它还执行了Page对象中的一些代码功能(即Button的事件体Button1_Click方法),就达到了我们要实现的功能。通过触动一个对象Button影响到另一个对象Page的行为,并且在Button的事件体中即可以修改Button本身(通过sender或this.Button1),也可以修改Page页面对象的其他控件或执行任意想要的代码功能。 其次从ASP.NET工作机制说一下它的工作原理,与桌面应用程序中的事件不同,ASP.NET服务器控件事件是在服务器上引发和处理的。当Web请求将客户端操作发送到服务器时,控件可以在服务器上引发事件来响应客户端操作。该页或其子控件对事件进行处理,然后ASP.NET将响应发送回客户端。这样,用户感觉就像在使用桌面应用程序一样。但是,控件开发人员必须了解只有一个客户端事件发送到服务器,即回发事件。 有些在客户端执行的事件(如JavaScript定义的客户端事件)没有被发送到服务器,不能被服务端处理,这样的事件不是我们这一章要讲的事件,本章讲的是服务器控件事件,是由服务器来处理的事件。 5.1.2 数据回发机制在ASP.NET技 术的服务器编程中,服务器处理完客户端的每个请求就认为任务结束,当客户端再次请求时,服务器会作为新的一次请求处理,即使是相同的客户端也是如此。也就 是说服务器不会保存我们两次请求之间的一些前后相接的数据,这就比较麻烦了,比如当我们输入一些信息到一个文本中,然后提交一个按钮,很多时候我们要在按 钮提交的服务端事件中处理提交之前的数据和提交按钮时用户输入的最新数据,即想同时得到文本框的旧值和新值,而服务端不会保存前一个请求的任何信息,那怎 样才能做到这一点呢? 两次页面请求之间的数据关联性问题,ASP.NET是通过视图机制实现的。简单地讲,视图区域信息(ViewState)存储在页面上的一个隐藏字段,里面存储每次需要视图机制保存的一些信息,每次提交时,它都会以“客户端到ó服务端”的形式来回传递一次,当处理完成后,最后会以处理后的新结果作为新的视图信息存储到页面中的隐藏字段,并与页面内容一起返回到客户端。后面会有针对视图状态机制的专门讲解,这里仅了解其功能即可。 有了视图机制,在其基础之上的数据回发机制就是完成处理视图信息的功能。具体过程是,服务端控件只要实现IPostBackDataHandler接口,则当客户端提交请求后,就会有机会利用IPostBackDataHandler接口的LoadPostData方法,在该方法内部处理子控件的新旧值逻辑,而视图信息数据这时以一个集合对象形式作为LoadPostData参数,并可以决定是否引发控件值变化后的事件。这就是要引入数据回发机制功能的原因。 通过上面两小节的讲解,您应该对事件和数据回发机制有了比较系统的认识。这样会较容易理解接下来要讲的事件和数据回发机制的具体使用和实践部分内容。 5.2 事件和数据回发机制的实现5.2.1 客户端回传事件接口IPostBackEventHandler要使控件捕获回发事件,控件必须实现System.Web.UI.IPostBackEventHandler 接口。此接口约定允许控件在服务器上引发事件来响应来自客户端的回发。IPostBackEventHandler接口包含一个方法。
.
参数eventArgument表示要传递到事件处理程序的可选事件参数,一般通过此参数可以确定不同的引发事件源,进而作不同的逻辑处理。在本章最后有个例子说明eventArgument参数用法。回发后,页框架就会搜索发送的内容,并确定发送的名称是否与实现IPostBackEventHandler的服务器控件的UniqueID对应。如果对应,页框架就会在该控件上调用RaisePostBackEvent方法(在引发更改事件后)。 以下代码片段显示了在服务器上引发Click事件的RaisePostBackEvent实现:
.
在该方法中主要完成调用OnClick(e)事件功能,其中包含两个参数:第一个参数为当前控件本身(即一般事件体中sender,类型一般为object);第二个参数e为ButtonEventArgs参数类型对象,是继承于System.EventArgs类实现的参数类,在该类中可以定义与代码逻辑相关的任意属性,作为事件体的参数。 最后,RaisePostBackEvent需要被客户端引发才能够执行,下面是一段能够引发服务端事件的代码:
.
这段代码输出一个HTML的button标签,并设置为提交类型。非常重要一点是,不要忘记设置其name属性,因为当回发后,页框架就会搜索发送的内容,并确定发送的名称是否与实现IPostBackEventHandler的服务器控件的UniqueID对应。如果对应,页框架就会在该控件上调用RaisePostBackEvent方法;反之,如果没有设置按钮的name值为UniqueID属性值,当单击按钮时页框架就不会引发该控件的RaisePostBackEvent方法,因为只有名称为UniqueID(服务器控件服务端ID)的按钮才会被注册为具有IPostBackEventHandler接口功能的控件。 下面通过一个简单的完整例子,了解事件回发处理机制,代码如下:
.
该控件内容比较简单,仅输出一个提交类型的按钮,由于这个按钮类型为submit,所以当单击按钮时,其本身已经可以提交事件到服务器,但仅仅这样主控件还不能够捕捉到该按钮事件。控件能够捕捉处理该事件需要具备两个条件:第一,主控件继承了IPostBackEventHandler接口以及实现了RaisePostBackEvent方法;第二,必须有name值为UniqueID的客户端标签,页框架只认识控件的name属性。只有这两个条件同时具备才能够使控件具备捕捉并处理事件的机会。 语句[DefaultEvent("Click")]的功能是定义Click事件为默认事件。通常把最常用的一个事件定义为默认事件,如果定义了默认事件,在设计器中双击控件时系统会自动从源代码视图(*.aspx)切换到后面代码(*.cs)页面,并可以自动注册默认事件;否则,仅切换到后台代码,但不注册任何事件。 类代码中最上面的一句: public event EventHandler Click; 这条语句定义了一个委托事件。EventHandler是一个预定义的委托,专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自定义事件数据类型,并且必须要么创建一个委托(其中第二个参数的类型为自定义类型),要么使用泛型EventHandler委托类并用自定义类型替代泛型类型参数。 在5.1.1节讲过事件的意义及其完成的功能,即在一个类中执行另一个功能类的功能,要想执行另一个类中的某个功能方法,必定要在当前类中保留另一个方法的引用,委托和事件就是为实现此功能而出现的。委托类似于C++中的函数指针,它一般指向一个方法。这样,当调用本类中的委托就相当于调用了另一个类中的方法。 若要将事件与处理事件的方法关联,请向事件添加委托的实例和委托事件实例,如: this.Button1.Click +=new EventHandler(Button1_Click); 除非移除了该委托,否则每当发生该事件时就调用事件处理程序。 另外,委托的应用比较广泛,还有更加复杂的应用,比如定义一个委托可以同时指向多个事件,即一个委托指定一个事件列表,当调用委托时可以执行一系列的事件。这里限于篇幅就不多讲了,更多关于事件委托概念请查看微软官方文档,那里面已经讲得很详细了。 5.2.2 客户端回发/回调揭密对于服务端控件元素,比如ASP.NET的Button标准服务端控件在提交时可以自动把请求发送到服务端处理,这样的控件我们不用自己处理它们的事件回发;但对于呈现不引起回发的HTML元素,如“文本框”(TextBox)或“链接按钮”(LinkButton),而希望由控件启动回发,则可以在ASP.NET中通过依靠客户端脚本的事件结构进行编程来实现这一功能。 完整地处理一个事件则还需要回发和捕捉。捕捉是IPostBackEventHandler接口的事情,上一节讲得比较清楚了,这一节主要讲回发(客户端回发请求到服务端)。 下面就以一些常用的HTML标记展开分析客户端回发机制,以及各种HTML标记的回发形式。 5.2.2.1 设置HTML Button标记的类型为submit 如上节的PostBackEventControl控件例子就是采用设置HTML Button标记的类型为submit的方式从客户端提交回发的。回发代码如下: output.Write("<INPUT TYPE=submit name=" + this.UniqueID + " Value='单击我' />"); INPUT是标准的HTML标记控件,它默认情况下没有runat="server",并且这些控件默认情况下只能处理一些客户端方法。INPUT的TYPE属性表示该标记的控件类型,如:type=button表示按钮;type=submit表示可提交表单功能的按钮;type=text表示文本框控件;type=file表示上传文件控件等。这里主要说明的是当type=submit类型的控件表示提交按钮时,它显示的样式与type=button的效果一样,不同的是单击它后可以提交表单到服务器,且不需要另外的代码,ASP.NET默认情况下是提交表单到当前页面。type=submit提交功能是从IE 3.0版开始支持的,只要浏览器版本不小于此版本都可使用该功能。 5.2.2.2 使用方法GetPostBackEventReference 得到回发脚本 1.为HTML客户端控件增加回发功能一般页面中的按钮并不都是type=submit类型的,大部分是type=button类型的按钮,把上面5.2.2.1节的代码段中的type=submit修改成type=button,修改后的代码如下: output.Write("<INPUT TYPE=button name=" + this.UniqueID + " Value='[使用提交按钮]' />"); u 由于此代码段中的按钮的type属性由sumbit改成了一般按钮类型button,则此按钮输出后将不再具有回发到服务端的功能。为了使一般按钮也具有回发的功能,ASP.NET提供了Page.ClientScript.GetPostBackEventReference方法。ClientScript类型为ClientScriptManager,该类主要功能是在Web应用程序中定义用于管理客户端脚本的方法。GetPostBackEvent Reference方法体结构如下: GetCallbackEventReference(String, String, String, String, String, Boolean) 此方法功能是获取一个对客户端函数的引用;调用该方法时,将启动一个对服务器事件的客户端回调。此重载方法的客户端函数包含指定的目标、参数、客户端脚本、上下文、错误处理程序和布尔值。 在期望不执行回发而从客户端运行服务器代码的情况下,可以使用ClientScriptManager类来调用客户端回调。这称为对服务器执行带外回调。在客户端回调中,客户端脚本函数向ASP.NET网页发送异步请求。网页修改其正常生命周期来处理回调。使用GetCallbackEvent Reference方法获取一个对客户端函数的引用,当调用该函数时,它将启动一个对服务器端事件的客户端回调。 使用GetCallbackEventReference方法对上面代码增加回调客户端功能,修正后的代码如下: output.Write("<INPUT type=button name=""{0}"" value='[使用Page.ClientScript对象方法]' onclick=""{1}"">", this.UniqueID, Page.ClientScript.GetPostBackEvent Reference(this, "")); Page.ClientScript.GetPostBackEventReference方法的第一个参数传递当前控件引用,在实际应用中可以传递任意控件引用,包括子控件;第二个参数为可选的命令参数,这里设置为null,一般同时处理多个按钮时可以设置该参数为不同的命令名称。 另外,Page.ClientScript对象还有个非常重要的方法GetCallbackEventReference。使用GetCallbackEventReference方法获取一个对客户端函数的引用,当调用该函数时,它将启动一个对服务器端事件的客户端回调,可以支持设置客户端回调方法名称等,在这里仅简单提一下,在后面还有专门章节介绍ASP.NET控件开发对客户端的支持。 转入正题,在代码中增加按钮的onclick单击事件,当单击按钮时会调用GetPostBackEvent Reference方法返回的一串客户端脚本,并且在页面中生成一个客户端方法和两个type=hidden的隐藏域控件。下面是以上代码呈现到客户端的HTML代码: <INPUT type=button name="PostBackFromClientControl1"value='[使用Page. ClientScript对象方法]'onclick="__doPostBack('PostBackFromClientControl1','')"> <div> <input type="hidden" name="__EVENTTARGET" /> <input type="hidden" name="__EVENTARGUMENT" /> </div> <script type="text/javascript"> //<![CDATA[ var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } //]]> </script> 从上面最终输出的HTML源代码可以看到,Button控件的单击事件会执行一个名为_doPostBack的方法,并且此方法也是自动生成在页面中的。在_doPostBack方法中把事件目标对象eventTarget(调用GetPostBackEventReference方法时传递的第一个参数,为当前控件)赋值给当前Form对象,把事件参数对象eventArgument(调用GetPostBackEventReference方法时传递的第二个参数),最后调用Form对象的提交方法,提交窗体,到这里就实现了我们想要的回发功能。 还要注意到以下两句自动生成的代码: <input type="hidden" name="__EVENTTARGET" /> <input type="hidden" name="__EVENTARGUMENT" /> 这是两个隐藏域类型控件,主要用来存储事件目标对象和事件参数对象的值。还有,在以上的script标记之间,有个嵌套的//<![CDATA[ //]]> 子句,该句与纯控件开发技术无关。该句的功能是通知HTML读取器下面这段不是HTML的内容,需要按附加的转义字符分开保存,以防代码解析出错。 2.为服务端控件生成回发脚本Page.ClientScript.GetPostBackEventReference方法还可以为服务端控件生成自己的回发脚本,可以从客户端回发。 ASP.NET标准的Button服务端控件最终生成的HTML标记无非也是生成一个type=submit类型的控件。对于服务端控件也可以设置客户端回发功能。比如在使用Button控件时把UseSubmitBehavior属性设置为false,则禁用按钮的自动提交功能,就可以使用GetPostBackEventReference方法返回Button控件的客户端回发事件脚本,代码如下所示: string strPostBackCode = this.Page.ClientScript.GetPostBackEventReference(button1,”edit”); u 然后把Button的客户事件与生成的回发事件脚本进行关联: this.button1.Attributes[“onclick”] = strPostBackCode; 其他服务端控件也是这样设置的。 5.2.2.3 使用方法GetPostBackClientHyperlink得到回发脚本 这种方式的原理与GetPostBackEventReference类似。该方法的功能是获取一个脚本引用,与前者有一点区别是在其开头附加一个javascript: 前缀,该前缀属于JavaScript基本语法,常用来在非脚本语言(如HTML)源代码中告诉浏览器该前缀后面的格式串作为JavaScript脚本语言来解析,如:javascript:alert(‘hello’)即表示弹出一个“hello”对话框。该引用可在客户端事件中回发到指定控件的服务器,回发时使用指定的事件参数和一个布尔值指示是否为事件验证注册该回发。方法体结构如下: GetPostBackClientHyperlink(Control, String, Boolean) 跟GetPostBackEventReference相同,第一个参数为事件目标对象;第二个参数为可选参数;第三个参数表示是否为验证注册回发事件。 下面看一个应用示例,代码如下:
string href = Page.ClientScript.GetPostBackClientHyperlink(this, ""); output.AddAttribute(HtmlTextWriterAttribute.Href, href); output.RenderBeginTag(HtmlTextWriterTag.A); output.Write("[使用Page.ClientScript对象的GetPostBackClientHyperlink方法]"); output.RenderEndTag();
u 与前面不同,这里是输出一个HTML的<a>标签。相信读者已经猜到GetPostBackClientHyperlink的应用场景了(通过方法名***Hyperlink就能够看得出它是专为哪个控件使用的功能)。直接看一下最终生成的客户端的HTML源代码:
<a href="javascript:__doPostBack('PostBackFromClientControl1','')">[使用Page.ClientScript对象的GetPostBackClientHyperlink方法]</a> <div> <input type="hidden" name="__EVENTTARGET" /> <input type="hidden" name="__EVENTARGUMENT" /> </div> <script type="text/javascript"> //<![CDATA[ var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } //]]> </script>
u 从上面代码可以看到,除了这句: href="javascript:__doPostBack('PostBackFromClientControl1','')" 链接触发方式与前面Button控件的onclick触发方式有些不同外,其他的代码完全正确一样。 另外,ASP.NET会自动处理可重用部分代码,比如当页面中有多个可提交元素时,处理页面提交的公共方法_doPostBack在当前页面中总是生成一个,不会生成冗余。 关于GetPostBackClientHyperlink方法的使用就讲这么多。本节主要讲几种常用HTML标签的调用客户端回发的方式以及客户端回发的原理。注意,使用GetPostBackEventReference方法和GetPostBackClientHyperlink方法定义客户端回发事件。这些方法启用客户端脚本函数,在调用这些函数时,它们将促使服务器向该页回发。客户端回发与客户端回调的区别在于网页处理客户端回发事件要用完一个正常的生命周期,而GetCallbackEventReference是异步请求,在客户端回调中,客户端脚本函数向ASP.NET网页发送异步请求,网页修改其正常生命周期来处理回调。两者调用是有些区别的。每个功能点在随书光盘中都有完整的示例代码。 5.2.3 回传数据处理接口IPostBackDataHandler先看一下一个典型的代码段。 5.2.3.1 数据回发和回传事件 IPostBackDataHandler接口用于检查提交给页面的数据,并确定数据是否在客户端修改过。当控件实现该接口,控件则自动具有了参与回传数据的处理能力。开发人员可以通过实现接口相关成员,完成针对回传数据的处理逻辑。 IPostBackDataHandler接口包含两个方法,其定义体如下:
RaisePostDataChangedEvent();
}
u IPostBackDataHandler接口将ASP.NET服务器控件定义为自动加载回发数据而必须实现的方法。LoadPostData方法用来检查提交给服务器的数据,根据控件状态数据和回发数据是否发生更改而判断是否调用RaisePostDataChangedEvent方法,如果返回true,则.NET Framework会自动调用RaisePostDataChangedEvent方法,在此方法中可以引发自己定义的事件。该方法共有两个参数,第一个参数postDataKey表示标志控件的关键字,第二个参数postCollection表示发送数据的集合,类型为NameValueCollection,以键值对(Key/Value)的形式提供值集合,可以通过以下格式访问内部数据: string value = postCollection[postDataKey]; u 有两种使用格式:第一,默认控件的名称键postDataKey作为集合的检索参数,postDataKey是主控件的名称;第二,如果控件中有其他控件或子控件,则需要通过子控件的UniqueID检索子控件回发的值,在postCollection集合中存储了所有回发控件的新数据值。比如,如果要检索通过CreateChildControls方法创建的控件的子控件的回发数据,则可以通过以下方式读取: postCollection[this.UniqueID + this.IdSeparator + “子控件ID”]; this.UniqueID是当前控件服务端ID;this.IdSeparator是服务端分隔符,默认为"$"; 子控件的UniqueID =主控件服务端UniqueID + 分隔符 + 子控件ID。 postCollection集合结构如图5-1所示。
图5-1 postCollection集合结构
图中AllKeys即为存储的所有的键,后面的KingTextBox1,btnCopy,TextBox1等都为当前页面中控件的UniqueID。其中第一个为页面中默认都包含的“__VIEWSTATE”隐藏变量。 LoadPostData方法的一个完整的例子如下:
.
ASP.NET页框架跟踪所有对此方法调用返回true的服务器控件,然后对这些控件调用RaisePostDataChangedEvent方法。 如果LoadPostData方法最后返回的值为true(返回true或false是由我们自己决定的),则页框架会自动调用RaisePostDataChangedEvent方法,在该方法中我们可以自定义需要引发的事件。一个数据回发事件例子如下:
.
OnTextValueChanged是定义的文本框值变化的事件。 数据回发处理和数据回发事件就讲这些,最后再总结一下其要点步骤: 设置主控件的name值为UniqueID。 实现LoadPostData方法,处理自己的数据比较逻辑,返回布尔值。 实现RaisePostDataChangedEvent,在该方法加入自定义事件。如上面的 OnTextValueChanged(EventArgs.Empty)。 u 如果LoadPostData方法返回true,页框架会自动调用 RaisePostDataChangedEvent方法。 这一节主要讲解了定义原始控件(非复合控件,即通过重写TagKey,AddAttributes ToRender,Render等方法)的数据回发处理和数据回发事件。对于现成的子控件或开发组合控件时就没必要这么麻烦,基于此ASP.NET的Page控件提供了处理机制,请看下面一节。 5.2.3.2 把控件注册为要进行回发处理的控件 如果要把某个控件注册为要进行回发处理的控件,则可以通过方法Page.Register RequiresPostBack实现,此方法仅有一个参数,表示要进行回发处理的控件的引用(不是控件的ID),类型为Control类。以下是一个应用例子:
.
很重要一点是:要注册的控件必须实现 IPostBackDataHandler接口,否则将引发HttpException,如图5-2所示。
图5-2 Http Exception提示
当控件实现IPostBackDataHandler接口时,该接口将可以进行回发数据处理,并可以引发任何回发数据已更改的事件,而且必须要在页生命周期的Page_PreRender事件中或该事件之前向页注册当前控件为要进行数据回发处理的控件。 5.2.3.3 数据回发及引发回发事件示例 通过之前讲解,或许您对IPostBackDataHandler接口已经比较理解了,本节主要以一个数据回发控件KingTextBox为例,实践验证一下IPostBackDataHandler是否真的能够完成它的功能。该控件完成的功能跟ASP.NET文本框控件TextBox相同,也就是说我们要自己做一个TextBox控件,不但要显示文本,而且能够执行数据回发事件。此控件看似简单,而事实上看起来比较简单的非复合控件越抽象越不好理解。 首先,还是看一看KingTextBox的完整代码:
, e);
} } }
u 主控件KingTextBox包含一个名称为Text的服务端属性,用来存储控件的显示文本。在后面的Render方法呈现一个HTML文本标记: <input type="text" name="KingTextBox1"value="" /> 注意到控件的name属性是必要的,因为KingTextBox中就只有一个文本框,所以把主控件的服务端this.UniqueID赋值给了文本框。并且,在呈现控件时,把定义的Text属性值赋给控件的value属性。在赋值时首先通过HttpUtility.HtmlEncode(Text)方法把Text值转换一下再赋给控件。这是由于Text是由用户输入的,要避免用户输入HTML标记的情况,即不管用户输入什么都要当作文本处理。而HttpUtility.HtmlEncode方法就是将用户输入字符串转换成HTML格式,将用户输入的HTML关键字转义为非HTML关键字字符。 下面来分析一下控件的数据回发部分代码。控件继承IPostBackDataHandler接口,并实现了LoadPostData和RaisePostDataChangedEvent方法。 LoadPostData代码逻辑如下:
.
首先,把控件当前的旧值保存到 strOldValue变量中,然后根据Key: this.UniqueID从postCollection(这一点很重要:其存储的值对应控件映射到HTML标记的value属性)中取出文本框的值存放到strNewValue变量中,这样此文本框回发请求的旧值和新值都取到了。下面的if条件语句功能是比较存储到变量中的这两个新旧值,如果两个值不相同,则表示用户修改了文本框的值,则要把strNewValue赋值给控件的Text属性,并返回true,让页框架自动调用数据回发事件方法RaisePostDataChangedEvent,使控件具有调用文本框值变化事件的机会;否则,如果新旧值相同,则不必修改控件Text属性值,直接返回false,通知页框架不调用RaisePostDataChangedEvent方法。接下来,RaisePostDataChangedEvent事件的实现代码如下:
RaisePostDataChangedEvent()
{ OnTextChanged(EventArgs.Empty); }
u 该方法在LoadPostData方法之后执行,如果上面的LoadPostData方法返回值为true,则页框架会调用本方法,方法体中仅有一句代码,即调用OnTextChanged方法。此方法代码如下:
.
此方法体中判断开发人员在页面使用KingTextBox时是否注册了TextChanged 事件,如果注册了就调用开发人员的事件逻辑。委托和事件在本章开始的5.1.1节已经对其原理和用法作了说明,这里就不再多说。 完善一下控件,由于控件仅有一个属性,也仅有一个事件,可以设置控件的类元数据属性,指定默认属性和默认事件,设置好后的代码如下:
.
另外,为了使控件保留扩展性。以上这些方法 LoadPostData,RaisePostDataChangedEvent和OnTextChanged都通过关键字virtual设置成了虚方法,并且至少设置成protected,这样就可以保证控件人员很容易从该控件继承,通过重载相应的方法来扩展现有控件功能,使控件具有很好的扩展性。对于扩展控件功能,后面会有专门介绍扩展GridView控件的章节。到现在为止KingTextBox已经完成了。对于此控件笔者专门作了个测试其功能的页面例子,如图5-3所示。
图5-3 测试King TextBox功能的页面
页面上左边放置一个KingTextBox,右边放置一个标准的ASP.NET TextBox。下面是两个按钮,功能是完成把值复制给对方,并清空自己的值。下面的Label是提示是否执行了KingTextBox的事件。 上面设计器对应的源代码如下:
<table style="width: 260px"> <tr> <td style="width: 75px"> King TextBox</td> <td style="width: 3px"> Asp.NET TextBox </td> </tr> <tr> <td style="width: 75px; height: 21px"> <cc1:KingTextBox ID="KingTextBox1" runat="server" OnTextChanged= "KingTextBox_TextChanged"> </cc1:KingTextBox> </td> <td style="width: 3px; height: 21px;"> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox></td> </tr> <tr> <td colspan= 1> <asp:Button ID="btnCopy" runat="server" Text="Copy>>>" OnClick= "btnCopy_Click" /></td> <td colspan= 1> <asp:Button ID="btnCopy2" runat="server" Text="<<<Copy" OnClick= "btnCopy2_Click" /></td> </tr>
</table> <br /> <asp:Label ID="Label1" runat="server" Width="325px"></asp:Label>
u 这里主要测试KingTextBox控件,因此只对此控件注册OnTextChanged事件:
<cc1:KingTextBox ID="KingTextBox1" runat="server" OnTextChanged= "KingTextBox_TextChanged">
u 对应的页面后台代码中的事件体,以及两个Button的事件代码如下:
.
事件体逻辑代码都比较简单,Button主要完成复制值的操作,KingTextBox_Changed事件执行后,会让页面中的Label控件显示提示文本。 OK,现在就可以运行一下页面,使用我们自己定制的KingTextBox控件,功能与ASP.NET的TextBox功能一样。 提示:为了更好地了解数据回发机制,在控件实现IPostBackDataHandler接口的几个方法中都设置断点,跟踪一下。对于LoadPostData,可以通过跟踪了解参数postDataKey值和postCollection的存储结构,能更深刻地了解回发机制。
|
请发表评论