1. 事件以及為什么需要事件驅動機制
在C#語言詳解一書中對事件的定義是“事件是一種使對象或類能夠提供通知的成員”,在這里換句話說就是頁面中已注冊事件的對象能夠對用戶的操作進行捕獲并處理。那么為什么需要引用事件機制呢?
大家都知道,如果在類A的實例對象中創建了一個類B的實例對象,那么在類A的實例對象中就可以通過該類B的實例對象調用類B公開的任何方法和屬性等。就像用戶Page對象中包含了創建了一個TextBox對象,Page對象就可以通過TextBox對象去調用Text屬性。但是如果需要在上述的TextBox對象中調用Page對象中的某些屬性或方法又該怎樣處理呢?顯然包含調用就行不通了,事件機制正好解決該問題。
現就TextBox的TextChanged事件來描述下。
首先需要在TextBox中聲明TextChanged委托,并通過頁面注冊將該委托和頁面類處理事件函數關聯起來
<asp:TextBox ID="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
?
(上述代碼就是將TextBox1的TextChanged事件注冊到頁面類中)。
然后在TextBox類體內調用TextChanged委托關聯的事件函數就可以了。這樣,當頁面類對象中TextBox控件的Text發生改變時TextBox首先執行完自己內部邏輯處理后,就會調用頁面類中OnTextChanged事件函數TextBox1_TextChanged(),而TextBox1_TextChanged()函數是頁面類中的一個方法,所以TextBox就可以在該函數內調用頁面類中的其他方法和屬性了。(當然也是可以調用TextBox1自己的)。
?
2. 回發的原理
Web開發的人員都知道,客戶端回發到服務端的事件只有一個,那么在服務端怎樣的區分用戶執行了怎樣的操作呢?這里首先從ASP.NET的頁面請求說起了。
在ASP.NET中處理頁面時,前后兩個頁面之間是無狀態連接的,也就是說客戶端的前后兩次請求是相互獨立的,服務端不會保存前一次請求的頁面狀態。如此就引入了視圖狀態機制(我會在以后的文章中和大家分享下ASP.NET開發的視圖狀態和控件狀態機制的研究樂趣)來處理前后兩次請求的邏輯處理,其原理就是在前一次請求發生后服務端將頁面的邏輯信息保存在一個隱藏的字段中回送到客戶端,當后一次請求發生時服務端首先取出該隱藏字段中的值并恢復到各個視圖控件中,等邏輯處理完后再將新的數據保存到該隱藏字段會送到客戶端,從而延續了兩次頁面之間的狀態信息。
本文中要說明的回發就是在視圖狀態機制基礎上完成的,也就是通過比較發送到服務端的控件當前值和保存在隱藏字段中的舊值,從而決定是否觸發哪些事件。當然,自定義的控件類必須通過繼承IPostBackDataHander接口來完成事件的回發功能。
?
3. 異步回調的原理
和回發不同,回調就是從客戶端到服務端,在服務端處理相關邏輯完了將處理的數據返回給客戶端,就相當于客戶端調用了服務端的方法(這和Web Service很像)一樣,將處理的數據作為返回的結果供客戶端程序處理。
異步回調過程中,ASP.NET會修改頁面正常的生命周期來處理請求,而回發則是ASP.NET會用一個完整的頁面生命周期處理請求。
另外,異步回調過程中,用戶還可以進行其他的操作,客戶端頁面并不會重新刷新。
?
4. 事件回發的實現
上面說過,客戶端到服務端的事件只有一個——回發事件。那么,服務端怎樣使自己的控件捕獲該回發事件呢?
你需要將你的控件類繼承IPostBackEvnetHander接口并實現該接口中唯一的一個方法:RaisePostBackEvent(string eventArgument)。
下面就以一個簡單的實例說明下:
?
(1).首先創建一個服務器控件項目和測試項目。(這里介紹了項目的創建方法,以后就不再說明了哦!)
打開VS2008新建項目,在彈出的對話框中選中ASP.NET服務器控件模板,輸入你項目名并選擇路徑,如下圖1。
圖1
然后在資源管理器中的“解決方案...”上右鍵“添加”=>“新建項目”(如圖2所示),彈出如圖1對話框,選中“ASP.NET Web應用程序”后輸入項目名(這里僅只為了測試控件的一些處理,所以命名為test)確定。
(2).繼承IPostBackEvnetHander接口并實現接口方法。
如下圖所示,ServerControl1繼承了IPostBackEvnetHander接口,將鼠標懸停在該接口上片刻會彈出實現接口方法提示對話框,如下圖3所示。
選擇“實現接口...”就可自動生成該接口需要實現的方法的空函數代碼。
#region IPostBackEventHandler 成員 public void RaisePostBackEvent(string eventArgument) { throw new NotImplementedException(); } #endregion
?
(3).接下來,就開始實現該方法了。
protected override void RenderContents(HtmlTextWriter output) { //output.Write(Text); output.Write("<input type='button' name=\"{0}\" value='[點擊我]' ></input>", this.UniqueID); } #region IPostBackEventHandler 成員 public void RaisePostBackEvent(string eventArgument) { //throw new NotImplementedException(); OnCliclk(EventArgs.Empty); } #endregion /* Click事件委托 */ public event EventHandler Click; protected virtual void OnCliclk(EventArgs e) { if (Click != null) { Click(this, e); } }
?
上述代碼中首先定義了一個Click委托和Onclick事件函數,用于與頁面注冊的單擊事件函數關聯。而在RaisePostBackEvent()方法中只調用了OnCliclk(EventArgs.Empty);方法,處理用戶的邏輯。
?
(4).在控件的RaisePostBackEvent()方法處打下斷點,并從工具欄中將上述控件(ServerControl1)拖放到test項目中的Default.asp頁面中,運行測試下。
單擊按鈕后,代碼的執行并沒有進入斷點處函數。為什么?
因為IPostBackEvnetHander接口只提供捕獲回發事件,但并沒有回發就談不上捕獲了(客戶端button類型的input按鈕不會觸發回發事件的)。將input類型該為submit類型再試下,這次可以了吧。
?
(5).控件自動回發實現
上面只用當按鈕類型改為submit類型時才能捕獲回發事件,但總不能任何的控件都需要帶上一個提交按鈕吧,別急,下面就說說控件的客戶端自動回發。
要實現控件客戶端的自動回發,可以采用兩種方式——通過GetPostBackEventReference或GetPostBackClientHyperlink方法獲取客戶端回發函數的引用。
修正上面的代碼如下:
protected override void RenderContents(HtmlTextWriter output) { //output.Write(Text); //output.Write("<input type='button' name=\"{0}\" value='[點擊我]' ></input>",// this.UniqueID); output.Write("<input type='button' name=\"{0}\" value='[點擊我]' onclick=\"{1}\"></input>", this.UniqueID, Page.ClientScript.GetPostBackEventReference(this, "")); }
?
或者
protected override void RenderContents(HtmlTextWriter output) { output.Write("<input type='button' name=\"{0}\" value='[點擊我]' onclick=\"{1}\"></input>" ,this.UniqueID,this.Page.ClientScript.GetPostBackEventReference(this,"")); }
?
運行程序,測試下,你的控件現在就可以自動回發了。
(6).雙擊Default.asp頁面中的【點擊我】按鈕,Default.asp.cs文件中為自動生成該按鈕的單擊事件函數。
protected void ServerControl1_Click(object sender, EventArgs e){ }
?
打上斷點,單步跟下,在該函數中可以調用頁面類中的方法和屬性了。這就是Button對象通過事件調用Page對象中的方法和屬性了。
?
5.異步回調的實現
異步回調的處理就不是控件封裝中的內容了,單為了讓大家能夠分清回發和回調的區別,還是講解下。
異步回調是在頁面類中處理的,同樣,頁面類需要繼承ICallBackEventHandle接口并實現接口方法來增加該頁面類的異步回調的功能。這里,就需要從JavaScript開始著手了,還是沿用上面的工程進行實例講解。
?
(1).在上面的Default.asp頁面中添加一個按鈕和用于顯示結果的Span元素。
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>無標題頁</title> </head> <body> <form id="form1" runat="server"> <div> <cc1:ServerControl1 ID="ServerControl1" runat="server" onclick="ServerControl1_Click" /> <br /><br /> <input id="ButtonCallBack" type="button" value="[實現了異步回調功能的Button]" /> <br /> <div>顯示異步回調兩個參數結果:<br /> Args1. <span id="ResultShowArg1"></span><br /> Args2. <span id="ResultShowArg2"></span> </div> </div> </form> </body> </html>
?
(2).Default.asp頁面后臺文件類繼承ICallBackEventHandle接口并實現接口方法。
public partial class _Default : System.Web.UI.Page, ICallbackEventHandler { protected void Page_Load(object sender, EventArgs e){ } protected void ServerControl1_Click(object sender, EventArgs e){ } #region ICallbackEventHandler 成員 public string GetCallbackResult() { throw new NotImplementedException(); } public void RaiseCallbackEvent(string eventArgument) { throw new NotImplementedException(); } #endregion }
?
(3).在頁面類的Page_Load事件函數中加入下面代碼:
protected void Page_Load(object sender, EventArgs e) { string cbRe = Page.ClientScript.GetCallbackEventReference(this, "arg", "CallBackServe", "context"); string callbackscript; callbackscript = "function CallPageServe(arg,context)" + "{" + cbRe + ";}"; Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CallPageServe", callbackscript, true); }
?
其中首先通過page對象引用ClientScript.GetCallbackEventReference()方法獲取該頁面的回調函數的應用,然后將該應用通過RegisterClientScriptBlock()方法注冊到客戶端頁面中。
這里需要說明下,其中GetCallbackEventReference方法的第一個參數是控件對象的引用,通常情況下是this;第二個和第四個參數(“arg”和“context”)是處理客戶端想服務端回調的函數CallPageServe函數的傳入參數;真正重要的就是第三個參數“CallBackServe”,這是回調過程中服務端處理完后返回到客戶端后處理返回數據的JS函數名。
(4).返回到Default.asp頁面,雙擊“ButtonCallBack”按鈕,為該按鈕注冊客戶端JS單擊事件函數ButtonCallBack_onclick()。
<script language="javascript" type="text/javascript"> function ButtonCallBack_onclick() { CallPageServe("args1", "args2"); } function CallBackServe(returnValue, context) { document.getElementById("ResultShowArg1").innerHTML = returnValue; document.getElementById("ResultShowArg2").innerHTML = context; } </script>
?
其中ButtonCallBack_onclick()中調用的CallPageSever()就是回調的觸發函數。而CallBackServe()就是客戶端處理回調返回數據的JS函數。
(5).回到Default.asp.cs文件,為該頁面類添加一個String 字符串對象,并在ICallBackEventHandle接口方法的函數體內做相應的處。
string returnValue = ""; #region ICallbackEventHandler 成員 public string GetCallbackResult(){ //throw new NotImplementedException(); return returnValue; } public void RaiseCallbackEvent(string eventArgument) { //throw new NotImplementedException(); returnValue = eventArgument; } #endregion
?
(6).運行檢驗下,單擊ButtonCallBack按鈕后顯示回調的結果。
在你感興趣的地方打上斷點單步跟下吧,你會發現還有其他有趣的東東。
?
還是附上全部源碼吧。
Default.asp.cs文件
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using PostBackEventControlDemo; namespace test { public partial class _Default : System.Web.UI.Page,ICallbackEventHandler { protected void Page_Load(object sender, EventArgs e) { string cbRe = Page.ClientScript .GetCallbackEventReference(this, "arg", "CallBackServe", ""); string callbackscript; callbackscript = "function CallPageServe(arg,context)" + "{" + cbRe + ";}"; Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CallPageServe", callbackscript, true); } protected void onClickHandle(object sender, EventArgs e) { String txt = ServerControl11.Text; int i = 0; } #region ICallbackEventHandler Members string returnValue = ""; public string GetCallbackResult() { return returnValue; //throw new NotImplementedException(); } public void RaiseCallbackEvent(string eventArgument) { returnValue = eventArgument; //throw new NotImplementedException(); } #endregion } }
?
Default.asp?
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="test._Default" %> <%@ Register assembly="PostBackEventControlDemo" namespace="PostBackEventControlDemo" tagprefix="cc2" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <cc2:ServerControl1 ID="ServerControl11" runat="server" OnClick="onClickHandle"/> <br /> <br /> <input id="ButtonCallBack" type="button" value="[實現了異步回調功能的Button]" onclick="return ButtonCallBack_onclick()"/> <div>顯示異步回調兩個參數結果:<br /> ARG1 <span id="ResultShowArg1" ></span><br /> ARG2 <span id="ResultShowArg222" ></span> </div> <script language="javascript" type="text/javascript"> function ButtonCallBack_onclick() { CallPageServe("args1|args2"); } function CallBackServe(returnValue) { var str = returnValue.split('|'); document.getElementById("ResultShowArg222").innerHTML = str[1]; document.getElementById("ResultShowArg1").innerHTML = str[0]; } </script> </div> </form> </body> </html>
?ServerControls1
using System; 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; namespace PostBackEventControlDemo { [DefaultProperty("Text")] [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] public class ServerControl1 : WebControl,IPostBackEventHandler { [Bindable(true)] [Category("Appearance")] [DefaultValue("")] [Localizable(true)] public string Text { get { String s = (String)ViewState["Text"]; return ((s == null) ? "[" + this.ID + "]" : s); } set { ViewState["Text"] = value; } } protected override void RenderContents(HtmlTextWriter output) { output.Write("<input type='button' name=\"{0}\" value='[點擊我]' onclick=\"{1}\"></input>" ,this.UniqueID,this.Page.ClientScript.GetPostBackEventReference(this,"")); } #region IPostBackEventHandler Members public void RaisePostBackEvent(string eventArgument) { OnClick(EventArgs.Empty); } #endregion public event EventHandler Click; protected virtual void OnClick(EventArgs e) { if(Click != null) { Click(this,e); } } } }
??
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
