1、 我们运行上章的页面,会按下按钮会发现我们写的事件无效,大家可能在上章写的时候已经发现了,只不过在写上章说明了下,只为了让控件增加个事件。为什么按下按钮无效呢?这意味着TestControl3控件没引发Test事件,也就同步说明控件没调用OnTest方法,因为OnTest方法的责任是引发事件。TestControl3控件无法获知页面回传以及希望其引发Test事件。页面负责通知服务器控件,已经发生回传,现在要考虑的是页面如何通知服务器控件告诉它页面已经回传了?每个Html元素都有一个name属性,当Html元素例如Submit按钮导致回传时,Html属性的name的值将被回传到服务器,以便服务器了解是哪个Html元素导致了回传。
2、 包含页面执行以下任务:(1)该页面返回导致回传的Html元素的name属性值,在TestControl3空间中,页面返回Btn_Show按钮的name属性值,因为Btn_Show导致回传的Html元素;(2)该页面检索控件树,查找其中那个与导致回传的Html元素的name属性具有相同值的服务器控件的UnqueID。在TestControl3控件中,页面会搜索UniqueID与Btn_Show按钮的name属性的值相同的服务器控件。(3)如果页面发现了具有指定UniqueID值的控件,那么它将检测该控件是否实现了IPostBackEventHandler接口,这个接口仅公开了一个RaisePostBackEvent方法。因此,实现该接口意味这控件公开了RaisePostBackEvent方法,如果控件实现了这个接口,那么页面将调用它的RaisePostBackEvent方法。控件在方法内实现的内容就是控件的任务。换言之,在调用RaisePostBackEvent方法之后,页面直到回传事件引发才会输出。
3、 关键是确认Btn_Show按钮的name属性与TestControl3控件的UniqueID属性具有相同的值,控件TestControl3继承了TestControl2,我们看下TestControl2的RenderContent的方法,下面代码显示了Btn_Show的部分代码,如代码里显示的ShowNameName属性值,与Btn_Show按钮的name属性具有相同的属性值。
writer.RenderBeginTag(HtmlTextWriterTag.Tr); writer.AddAttribute(HtmlTextWriterAttribute.Colspan, "2"); writer.AddAttribute(HtmlTextWriterAttribute.Align, "center"); writer.RenderBeginTag(HtmlTextWriterTag.Td); writer.AddAttribute(HtmlTextWriterAttribute.Id, ShowNameId); writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit"); writer.AddAttribute(HtmlTextWriterAttribute.Value, ShowNameLable); writer.AddAttribute(HtmlTextWriterAttribute.Name, ShowNameName); writer.RenderBeginTag(HtmlTextWriterTag.Input); writer.RenderEndTag(); writer.RenderEndTag(); writer.RenderEndTag();
protected virtual string ShowNameName { get { return "Btn_Show"; } }
4、 这表明了TestControl2的RenderContent方法将字符串“Btn_Show”设置为Btn_Show按钮的name属性值,现在就要确认Btn_Show按钮的name与TestControl2控件的UniqueID属性具有相同的值。问题是谁设置了控件的UniqueID的值?答案还是包含页面。因为Btn_Show按钮的name属性值与TestControl2控件的属性值是由两种不同的实体来设置,具体而言是RenderContent方法和包含页面,所以它们绑定值的方式不同。
5、 说了这么多理论的东西,也未必能了解和懂,总结起来归根到底就是,点击Btn_Show,控件TestControl3没有引发Test事件有两个原因,一是控件的UniqueID的值不等于字符串“Btn_Show”,这也就是页面搜索该UniqueID值的服务器控件失败的原因,因为我们看到ShowNameName这个属性是只读的只有包含页面才能设置,所以我们只能重写这个属性让控件返回UniqueID的值,重新定义个控件叫TestControl4继承TestControl3;第二个原因就是TestControl3没有事件我们上面所说的IPostBackEventHandler接口,这样就好理解了。
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace WebFormControl{ public class TestControl4:TestControl3 { protected override string ShowNameName { get { return this.UniqueID; //return base.ShowNameName; } } }}
6、 由于TestControl4控件必须实现IPostBackEventHandler接口,该接口必须实现RaisePostBackEvent方法,显示实现IPostBackEventHandler接口,定义一个受保护的虚拟方法,以便调用。
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { this.RaisePostBackEvent(eventArgument); //throw new NotImplementedException(); } protected virtual void RaisePostBackEvent(string eventArgument) { TestEventArgs3 e = new TestEventArgs3(Name); OnTest(e); }
7、 RaisePostBackEvent之前,我们要定义1个属性和1个字段,Name属性是来保存文本框Txt_Name,HasName字段是来判断Txt_Name是否修改。
private bool HasName; protected virtual string Name { get { return ViewState["Name"] != null ? (string)ViewState["Name"] : string.Empty; } set { ViewState["Name"] = value; } }
8、 实现了RaisePostBackEvent方法,如何从方法中获取信息呢?控件还必须实现另外个接口IPostBackDataHandler接口,以便访问表单数据,该接口有两个方法要实现LoadPostData和RaosePostDataChangEvent。这里也显示实现IPostBackDataHandler接口,写两个受保护的虚拟方法调用。
bool IPostBackDataHandler.LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection) { return this.LoadPostData(postDataKey, postCollection); //throw new NotImplementedException(); }void IPostBackDataHandler.RaisePostDataChangedEvent() { this.RaisePostDataChangedEvent(); //throw new NotImplementedException(); }
9、 当用户点击Btn_Show按钮,将表单数据回传给服务器,页面将调用LoadPostData方法,并将包含回传表单数据的NameVauleCollection类型的集合传递给该方法。这个集合针对每个Html元素都存储了一个对应的名称/值对,其中名称包括元素的name属性值,值包括回传给服务器的值。换言之,集合包括1个与TestControl4中的1个表单元素相对应的名称/值对。因此LoadPostData方法必须将每个Html元素的name属性值作为NameVauleCollection的索引,从而实现对相关元素的访问。
protected virtual bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection) { string name = postCollection[NameName]; if (name != Name) { Name = name; HasName = true; } if (!string.IsNullOrEmpty(postCollection[ShowNameName])) Page.RegisterRequiresRaiseEvent(this); return HasName; }
10、 LoadPostData方法返回一个bool值,当LoadPostData返回true时也就是说TestControl4属性发生了变化,页面才会调用RaosePostDataChangEvent方法。
protected virtual void RaisePostDataChangedEvent() { if (HasName) { //可以在这个写个事件针对文本框值变化时写个事件,我们这里就不写了,我们主要先实现我们第一章的效果 } }
11、 把TestControl4拖到页面上按照上一章第5点设置,我们就可以实现我们的最开始的效果,点击按钮,弹出对话框(效果图详见第一章(1)的第1点)就是界面样式不一样哈。
在成功的背后还是有个问题存在,如果我拖两个控件,看下Html源码就会发现问题,很多ID等属性一样了。在控件内这些属性都是唯一的,但是在页面里就不是了,如何在页面中保证Html元素的唯一呢?我们重写控件的属性让(UniqueID+”_”+原始名)来保证唯一性。我们在看下Html源码就不会出现重复了。
<table id="TestControl41" style="border-collapse:collapse;"> <tr> <td>姓名</td><td><input id="Txt_Name" type="text" name="Txt_Name" /></td> </tr><tr> <td colspan="2" align="center"><input id="Btn_Show" type="submit" value="显示" name="TestControl41" /></td> </tr></table> <table id="TestControl1"> <tr> <td>姓名</td><td><input id="Txt_Name" type="text" name="Txt_Name" /></td> </tr><tr> <td colspan="2" align="center"><input id="Btn_Show" type="submit" value="显示" name="TestControl1" /></td> </tr></table>
protected override string ShowNameName { get { return this.UniqueID + "_Btn_Show"; //return base.ShowNameName; } } protected override string ShowNameId { get { return this.UniqueID + "_Btn_Show"; } } protected override string NameName { get { return this.UniqueID + "_Txt_Name"; } } protected override string NameId { get { return this.UniqueID + "_Txt_Name"; } }
<table id="TestControl41" style="border-collapse:collapse;"> <tr> <td>姓名</td><td><input id="TestControl41_Txt_Name" type="text" name="TestControl41_Txt_Name" /></td> </tr><tr> <td colspan="2" align="center"><input id="TestControl41_Btn_Show" type="submit" value="显示" name="TestControl41_Btn_Show" /></td> </tr></table> <table id="TestControl1"> <tr> <td>姓名</td><td><input id="TestControl1_Txt_Name" type="text" name="TestControl1_Txt_Name" /></td> </tr><tr> <td colspan="2" align="center"><input id="TestControl1_Btn_Show" type="submit" value="显示" name="TestControl1_Btn_Show" /></td> </tr></table>