上一篇文章介绍了一个复合控件,下面将介绍ListPicker自定义控件,ListPicker是一个较复杂的自定义控件。因为它需要和脚本代码进行交互。
ASP.NET 服务器控件可以发送两种客户端脚本:客户端脚本块 和客户端 HTML 属性
客户端脚本块通常是用 JavaScript 编写的,其中通常包含在发生特定的客户端事件时执行的函数。客户端 HTML 属性提供将客户端事件与客户端脚本联系在一起的方法。例如,以下的 HTML 页面中包含了客户端脚本块,脚本块中包含了名为 doClick() 的函数。该页面同时还包含一个按钮(通过 <input> HTML 元素创建),这个按钮的 onclick 属性与 doClick() 函数绑定。也就是说,只要用户单击该按钮,就开始执行 doClick() 函数中的客户端代码。在本示例中,将显示一个弹出式对话框。 如下:
<html>
<body>
<form>
<script language="JavaScript">
<!--function doClick() {alert("You clicked me!");} -->
</script>
<input type="button" onclick="doClick()" value="Click Me!" />
</form>
</body>
</html>
对于以上 HTML 页面中的客户端脚本,客户端脚本块包含在 HTML 注释(<!-- 和 -->)中。之所以这样,是因为如果不将脚本块放入 HTML 注释中,那些不能识别脚本的旧式浏览器就会显示 <script> 块的内容。
在上面的 HTML 页面中,<input> 元素的 onclick 属性绑定到 doClick() 函数,因此在单击该按钮时将执行 doClick() 函数。
在使用客户端脚本时,只有两个 HTML 窗体元素(“按钮”(Button) 和“图像按钮”(ImageButton))引起窗体回发。如果自定义控件呈现不引起回发的 HTML 元素(如“文本框”(TextBox) 或“链接按钮”(LinkButton)),而您希望控件启动回发,则可以在 ASP.NET 中通过依靠客户端脚本(JScript、JavaScript)的事件结构进行编程来实现这一功能。
要启用这种回发机制, 启动回发必须在控件 Render 方法中添加的代码,示意如下。
protected override void Render(HtmlTextWriter output) {
output.Write("<a id=\"" + this.UniqueID + "\" href=\"javascript:" + Page.GetPostBackEventReference(this) +"\">");
output.Write(" " + this.UniqueID + "</a>");
}
代码里GetPostBackEventReference 方法发出启动回发的客户端脚本,并且还提供对启动回发事件的控件的引用。如果控件要捕获回发事件(来自客户端的窗体提交),它必须实现 System.Web.UI.IPostBackEventHandler 接口。此接口向 ASP.NET 页框架发出信号,指出控件希望收到回发事件通知。RaisePostBackEvent 方法允许控件处理该事件和引发其他事件。
通常我们可以使用GetPostBackEventReference传递参数,请看下面示意代码:
using System;
using System.Web;
using System.Web.UI;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI.WebControls;
namespace Ctrl {
public class Ctrl : Control, IPostBackDataHandler, IPostBackEventHandler {
private int _value = 0;
public int Value { get { return _value; } set { _value = value; } }
public bool LoadPostData(String postDataKey, NameValueCollection values) {
_value = Int32.Parse(values[this.UniqueID]);
return false;
}
public void RaisePostDataChangedEvent() { }
public void RaisePostBackEvent(String eventArgument) {
if (eventArgument == "加") { this.Value++; }
else { this.Value--; }
}
protected override void OnPreRender(EventArgs e) { }
protected override void Render(HtmlTextWriter output) {
output.Write("<h3>值:<input name=" + this.UniqueID + " type=text value=" + this.Value + "> </h3>");
output.Write("<input type=button value=加
OnClick=\"jscript:"+ Page.GetPostBackEventReference(this, "加")+ "\"> |");
output.Write("<input type=button value=减
OnClick=\"jscript:"+ Page.GetPostBackEventReference(this, "减")+ "\">");
}
}
}
当时用GetPostBackEventReference输出脚本时,它的两个参数分别是:this和加(减),其中this用于表示回发控件本省,当我们使用如下方式使用该自定义控件时
<%@ Register TagPrefix="Ctrl" Namespace="Ctrl" Assembly="Ctrl" %>
<html>
<body>
<form method="POST" action="NonComposition2.aspx" runat=server>
<Ctrl:Ctrl id="MyControl" runat=server/>
</form>
</body>
</html>
如果浏览服务器发给客户端的代码,就如下:
<html>
<body>
<form name="_ctl0" method="POST" action="Ctrl.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtMTAwOTQyOTI0Nzs7PomXs73t9xBOKEFKyYyTmD+75ZXL" />
<h3>值:<input name=MyControl type=text value=0> </h3><input type=button value=加 OnClick="jscript:__doPostBack('MyControl','加')"> |<input type=button value=减 OnClick="jscript:__doPostBack('MyControl','减')">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<script language="javascript" type="text/javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf("microsoft") > -1) {
theform = document._ctl0;
}
else {
theform = document.forms["_ctl0"];
}
theform.__EVENTTARGET.value = eventTarget.split("$").join(":");
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
</form>
</body>
</html>
本例中用粗体显示的元素(两个隐藏字段和客户端脚本方法)是由 ASP.NET 页框架添加的。隐藏窗体字段指示要发送到哪个服务器控件,并可以指定一个要传递的参数。客户端脚本方法用于设置隐藏字段,并将窗体提交给服务器.
所以,我们可以在Ctrl自定义控件里使用
public void RaisePostBackEvent(String eventArgument) {
if (eventArgument == "加") { this.Value++; }
else { this.Value--; }
}
的代码获取参数并进行判断。
在ListPicker.cs源代码里还调用Page的RegisterHiddenField额外注册了两个隐藏元素,如下
Page.RegisterHiddenField(SelectedHelperID, String.Join(",", SelectedItems ));
Page.RegisterHiddenField(AllHelperID, String.Join(",", AllItems ));
那么为什么要注册这两个隐藏控件哪?
我们先通过一个简单的javascript代码进行说明,下面是一段较为简单的脚本控件,它用于同步用户的选项,如下:
<SCRIPT LANGUAGE="javascript">
function setcity() {
switch (document.shengshi.shengID.value) {
case '江苏' :
var labels = new Array("南京","无锡","苏州");
var values = new Array("nj","wx","sz");
break;
case '安徽' :
var labels = new Array("合肥","蚌埠","淮北");
var values = new Array("hf","bb","hb");
break
}
// 清空市列表选择框的内容
document.shengshi.shiID.options.length = 0;
// 从数组中添加内容
for(var i = 0; i <labels.length; i++) {
document.shengshi.shiID.add(document.createElement("OPTION"));
document.shengshi.shiID.options[i].text=labels[i];
document.shengshi.shiID.options[i].value=values[i];
}
// 选择第一个选项
document.shengshi.shiID.selectedIndex = 0;
}
</SCRIPT>
</HEAD>
<BODY>
<FORM NAME="shengshi">
省:
<SELECT NAME="shengID" OnChange="setcity()">
<OPTION value="江苏">江苏</OPTION>
<OPTION value="安徽" SELECTED>安徽</OPTION>
</SELECT>
市:
<SELECT NAME="shiID"></SELECT>
</FORM>
<!-- 执行初始化选择列表 -->
<SCRIPT LANGUAGE="javascript">
setcity();
</SCRIPT>
运行这段代码,当你在左边下拉框选择不同的省份,相应的右边下拉框里显示相应省份里的市区,但是现在有一个问题,不管你新选择哪个省份或者市区,你查看它的源代码,它生成的HTML代码保持不变,所以如果将该数据提交到服务器后,你得到的输出结果永远都是空值。
这是因为javascript是由浏览器内部运行机制实现的,并不体现在HTML代码上,所以为了解决这个问题,我们同样需要使用javascript保存用户的选项,后面读者可以看到ListPicker通过注册隐藏字段实现运行。这个是后面ListPicker实现的核心
事实上,也是我们常用的DropDownList、ListBox等实现的核心方式。(未完)