您的位置:首页 > 其它

复合控件和事件(3)——事件基础

2007-07-09 22:57 423 查看
上一篇:复合控件和事件(2)——属性,页面要回发,属性要保存
【本文的例子以CompositeControl来命名,但不代表本文是描述复合控件,只是这个系列都在描述这个而已,本文在描述的是控件的制作过程而非复合控件,因此命名只是为了保持解决方案的美观(真不理解自己为了美而放弃了事实,大家就将就一下哈!记住这不是复合控件只是控件)至于在复合控件的文章里面提到这个,理由在文章的和字之后,也就是事件。所以请您仔细阅读咯。后续的文章将会建立在此基础之上。】
每次当我们拿到Button的时候我们可以非常轻松地拖动它到页面,然后双击它就可以出现以下代码:

protected void Button1_Click(object sender,EventArgs e)
可以为事件提供返回类型,但这会出问题。这是因为引发给定的事件,可能会调用好几个事件处理程序。如果这些处理程序都返回一个值,那么我们该用哪个返回值

系统处理这个问题的方式是,只允许访问由事件处理程序最后返回的那个值,也就是最后一个订阅该事件的处理程序返回的值
这个功能在某些情况下是有用的,但最好不要使用它们。推荐使用void类型的事件处理程序,且避免使用out类型的参数。
因此我们应该在这里使用void,Button1_Click是一个事件处理程序的名字,其实只是名字,你要换成AAABBBCCC也是可以的,当然你一定不会这么做的。object sender,EventArgs e在MSDN里面讲的很清楚了,就直接贴了:引发事件的源和该事件的数据。(事件和委托)说白了就是谁引发了事件,传递了什么数据。系统定义了一个EventArgs作为事件数据的基类,你可以定义自己的事件数据类,用于在事件处理的过程中传输数据。
那当你按下按钮的时候是如何得知事件被触发的呢?
先不要管“按钮按下的时候”这个关键字,先来看看事件一般都是如何触发的。
首先会有一个委托名为

public delegate void ××××EventHandler(object sender, ×××EventArgs e)
然后定义一个该委托类型的事件:

public event ××××EventHandler EventName
之后还有一个事件处理程序:

protected void OnEventName(×××EventArgs e)
protected void OnEventName(×××EventArgs e)
ClassName obj = new ClassName();

//订阅事件
obj.××××EventHandler += new ××××EventHandler(
Method_Handler);
或者C#2.0内可以直接简化为:
obj.××××EventHandler += Method_Handler;

//事件处理程序
protected void Method_Handler(object sender, ×××EventArgs e)
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Button" />

转换为:

<input type="submit" name="Button2" value="Button" id="Button2" />

这个过程看上去像是翻译,而事实上在控件的内部是将asp的那些代码中的Attribute填充到Html的控件中,比如Text="Button",就被映射到了value,runat="server"则体现了type="submit"的HTML标签,这些HTML标签从服务器传回了客户端,客户端通过IE等浏览器,对HTML进行解析,就可以得到我们所见到的丰富多彩的网页了。
说到这里你似乎还是没法理解“按钮按下的时候”这个问题,其实当HTML在传回到客户端后就和服务器之间失去了联系,那么“按钮按下的时候”事实上只是一个完全客户端的事情。如果你对HTML有所了解的话你会发觉这里的type="submit"指示了这个按钮不是只代表了一个看上去很有弹性的方块,它还表示它按下的时候与type="button"类型按钮的区别在于它会导致整个页面回发(仔细看浏览器下方中间(如果是IE的话)的进度条会有一次刷新,如果是button则没有任何反应)。页面回发会向服务端再次请求当前页面,也就是这次请求让客户端与服务端再次地彼此联系。关于页面回发,下面是MSDN的一些描述:

回发和往返行程
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_aspnetcon/html/ed82ea8a-1439-4289-85d6-5ac86a012dbb.htm

ASP.NET 页面作为代码在服务器上运行。因此,要得到处理,页面必须配置为当用户单击按钮(或者当用户选中复选框或与页面中的其他控件交互)时提交到服务器。每次页面都会提交回自身,以便它可以再次运行其服务器代码,然后向用户呈现其自身的新版本。

ASP.NET 网页的处理循环如下:

1.用户请求页面。(使用 HTTP GET 方法请求页面。)页面第一次运行,执行初步处理(如果您已通过编程让它执行初步处理)。

2.页面将标记动态呈现到浏览器,用户看到的网页类似于其他任何网页。

3.用户键入信息或从可用选项中进行选择,然后单击按钮。(如果用户单击链接而不是按钮,页面可能仅仅定位到另一页,而第一页不会被进一步处理。)

4.页面发送到 Web 服务器。(浏览器执行 HTTP POST 方法,该方法在 ASP.NET 中称为“回发”。)更明确地说,页面发送回其自身。例如,如果用户正在使用 Default.aspx 页面,则单击该页上的某个按钮可以将该页发送回服务器,发送的目标则是 Default.aspx。

5.在 Web 服务器上,该页再次运行。并且可在页上使用用户键入或选择的信息。

6.页面执行您通过编程所要实行的操作。

7.页面将其自身呈现回浏览器。

数据终于千里迢迢从客户端回到了服务端。服务端又如何知道该怎么做呢?由于是回发事件,了解回发的控件是必须要实现IPostBackEventHandler接口的(如果是类似TextBox中数据改变等的则需要实现IPostBackDataHandler接口。控件可引起回发,使用 RaisePostBackEvent 方法捕获回发。RaisePostBackEvent 是IPostBackDataHandler接口的一个方法,也是唯一的一个公共方法。RaisePostBackEvent当由类实现时,使服务器控件能够处理将窗体发送到服务器时引发的事件。也就是当有回发发生的时候会调用这个程序。我写了一个代码来验证它:

writer.Write("<input id=\"Text1\" name=\"" + this.UniqueID + "\" type=\"text\" value=\"" + this.Text + "\" /><br>");
另外添加一个Text属性和实现IPostBackDataHandler的方法:

public String Text

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
就在这个postDataKey中,记得我们是如何命名我们的控件么?不记得了那就让我们看看最后生成的控件HTML:

<INPUT TYPE=button name=CompositeControl3_1 Value='确定' />
<input id="Submit1" name="CompositeControl3_1" type="submit" value="submit" /><br>
<input id="Text1" name="CompositeControl3_1" type="text" value="" />
其中它们都是name为CompositeControl3_1,其中Type=button的由于只是处于客户端的一个按钮,它的name其实无什么作用(name通常只被用于服务端控件,id则用于客户端)。而后两个name我们都是通过this.UniqueID来将其赋值的,这些让我们有些遭殃。因为postDataKey在断点时表现为CompositeControl3_1,而LoadPostData则通过postCollection来处理。如果只有一个Text就无所谓,但这里又多了一个Button也是同名的,这时候postedValue的值会将所有这个名字的服务端控件的value值用“,”(逗号)连接起来,类似:submit,填写结果

【MSDN】postCollection :所有传入名称值的集合。

改造1(无意义):可以将其name做一些手脚,让与Text的name区别于别人,这样可以达到效果,但是在之后的实现中,事件处理程序将不可用。理由是“它已经不再是它本身,本身的事件处理程序自然不属于它管”。
改造2:只取需要的数据:(将Text框内的数据单独取出)

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)

private string GetText(string controlText,int textIndex)
Page.RegisterRequiresRaiseEvent(this);

这句话加在哪里呢?既然是IPostBackDataHandler捕获了,而它捕获后的第一件事就是LoadPostData,因此我就将它放置于此了。由于是Page的一个方法,则必然Page要存在,当然Page一定是在的,因为是回发嘛,回发一定有页面存在咯。所以有:

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
writer.Write("<input id=t" + this.UniqueID + " name=" + this.UniqueID + " type=\"text\" value=\"" + this.Text + "\" /><br>");
一切就恢复正常了。
原因是我们的页面中只有一个name=CompositeControl3_1,也只有一个name=CompositeControl3_2。但是如果我们补充了上面这句,则上面两个name的标签将出现两次,类似:

<INPUT TYPE=button name=CompositeControl3_1 Value='确定' /><input id=CompositeControl3_1 name=CompositeControl3_1 type="submit" value="submit" /><br><input id=CompositeControl3_1 name=CompositeControl3_1 type="text" value="" /><br>
我们平时是如何使用asp控件的时候我们是不允许它们的ID相同的,也就是最后生成的页面中name必须是唯一值:

//aspx页面中添加以下代码,将会出现设计时错误以及编译不通过的尴尬境地
<asp:Button ID="Button1" runat="server" Text="Button" />
<asp:Button ID="Button1" runat="server" Text="Button" />
每个页请求只能注册一个服务器控件。当窗体发送数据中不包括控件的控件 ID 时,必须使用 RegisterRequiresRaiseEvent。而且,注册的控件必须实现 IPostBackEventHandler 接口。
也就是说这里只会执行CompositeControl3_2的事件。因此其CompositeControl3_1的ControlClick就会doesn't work了。

将Render中TextBox的语句修改为:

writer.Write("<input id=" + this.UniqueID + "_t name=" + this.UniqueID + "_t type=\"text\" value=\"" + this.Text + "\" /><br>");
运行的结果配合:Page.RegisterRequiresRaiseEvent(this);的结果则是OK的。理由是因此之前控件name重名了,LoadPostData就会被执行N次,N=控件的数量。也就是每次都会有不同的postDataKey进来,分别是CompositeControl3_1、CompositeControl3_2……它会将所有Post的数据进行一次检查,Page.RegisterRequiresRaiseEvent(this);的最终结果也就是最后一个事件了。但是如果名字正确会怎样呢?答案就是按哪哪应。也就是LoadPostData只执行一次,因此Page.RegisterRequiresRaiseEvent(this);的结果也就是当前的那个按钮了。至此我们可以配合以下代码正确运行程序了。

Page.RegisterRequiresRaiseEvent(this);

String presentValue = Text;
String postedValue = postCollection[postDataKey + "_t"];
而且因为Page.RegisterRequiresRaiseEvent(this);这个句子是在页面回发的时候才执行的,而执行的过程中我们保证了它只被用于当前控件,因此所谓的每个页请求只能注册一个服务器控件在这里就可以得到满足了。

也正是因为名字的唯一化,GetText的函数从此可以直接注释掉咯!

方案二:既然我们可以按哪哪应,而且不管是IPostBackEventHandler还是IPostBackDataHandler 都会感应到回发事件,一旦回发,分别会有RaisePostBackEvent和LoadPostData为它们响应。看看IPostBackEventHandler的作用也无非就是将相关事件进行一次处理而已,Page.RegisterRequiresRaiseEvent(this);的唯一目的也就是将RaisePostBackEvent执行一遍,因此可以将Page.RegisterRequiresRaiseEvent(this);的位置直接替换成OnControlClick(new ControlEventArgs());——>测试,通过!

public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
public class ControlEventArgs : EventArgs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace ComponentWebControls

CompositeControl3.aspx

<!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>CompositeControl3</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:CompositeControl3 ID="CompositeControl3_1" runat="server" OnControlClick="CompositeControl3_1_ControlClick" OnControlTextChanged="CompositeControl3_1_ControlTextChanged">
</cc1:CompositeControl3>
<br />
<asp:Label ID="LabelResult" runat="server" Text="页面刚加载!"></asp:Label><br />
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Button" /> <br />
<cc1:CompositeControl3 ID="CompositeControl3_2" runat="server" OnControlClick="CompositeControl3_2_ControlClick" OnControlTextChanged="CompositeControl3_2_ControlTextChanged">
</cc1:CompositeControl3>
<br />
<cc1:CompositeControl3 ID="CompositeControl3_3" runat="server" OnControlClick="CompositeControl3_3_ControlClick"
OnControlTextChanged="CompositeControl3_3_ControlTextChanged">
</cc1:CompositeControl3>
<br />
<br />
</div>
</form>
</body>
</html>

CompositeControl3.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace WebAppTestControls

CompositeControl3.aspx.designer.cs

//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行库版本:2.0.50727.42
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------

ControlEvent.cs

using System;

namespace ComponentWebControls

至此事件与控件的结合就显得相对明晰了。本文描述了详细的演化过程,大家会不会觉得有点混乱呢?如果会,请一定回头跟着演练,因为我的表达不保证到位,希望大家至少能够意会,别让我的言传让您误会噢~!^.^

有一篇文章不错,推荐给大家Understanding ASP.NET View State其中对页面生命周期的描述相当地详尽,其中一些涉及的知识点都是很有用的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: