摘要在本系列文章中我们首先讨论使用 构建一个标准ListBox控件的增强版本(EnhancedListBox)这个控件能够对它的项进行重排序并且能够实现客户端与服务器端的同步功能然后我们把这样的两个控件组合起来创建一个复合控件(ListMover)
一 引言
构建提供丰富的客户端接口的复杂Web控件经常需要把一些客户端JavaScript代码与控件的服务器端代码集成到一起然而在一些情况下为了达到某种巧妙的效果而把问题搞得过于复杂经常会破坏控件的内部服务器代码与生成的客户端HTML代码之间的数据同步而当进行页面回寄时这将成为一个问题在本文中我将首先构建两个很酷的Web控件(都极容易导致这一问题)然后向你展示如何来修改这一脆弱性
本文中我们将使用C#+ASPNET 来定制这些控件并在后面向你简短介绍如何使之工作在ASPNET (或)环境中
当前HTML仍然保持为Web应用程序生成阶段的主要语言遗憾的是它所使用的协议是无状态的所以必须由Web开发者自己来处理这种无状态特点通过使用一些架构特征例如回寄机制和ViewState变量ASPNET有助于处理这个问题然而为了实现某些功能还需要再作努力从而借助于回寄事件把Web页面不断向服务器发出请求的各种技术结合起来
具体地说我将分析如何使用JavaScript和DHTML存取在客户端生成的元素其实把客户端和服务器功能融合到一起要求使用大量的技巧才能达到最佳用户体验效果而微软在其ASPNET校验控件中就实现了这一点为了提供一种丰富的客户端校验效果该控件中使用了大量的JavaScript
作者注本文假定你对定制Web控件开发有一个基本了解因此我将不再重复Web控件开发的基础内容例如属性工作原理与风格的添加方式
二 一种常规实现方法
下面我想向你展示如何构建一组很酷的控件它们具有你在商业控件中才能看到的优秀功能稍后我将继续展示定制Web控件带给Web编程的完全封装优点既然你已经了解如何开发定制Web控件那么你应该知道的一个概念是封装一个控件所有的功能和行为(就象你在一个标准业务对象中所实现的那样)在学习构建具有复杂行为的控件时这种封装将极有用处
在第一个控件中我将向你展示如何构建一个称为EnhancedListBox的控件这个控件将扩展ASPNET的ListBox控件—添加一个头部和一些重排序按钮注意这是一个直接继承自常规ListBox的控件
之后我还将向你展示如何构建一个复合控件—ListMover它将包含两个上面提到的EnhancedListBox控件这个ListMover控件还包含一些允许你从一个列表到另一个列表中移动项的按钮
其实用常规方法(非面向Web控件的)来实现这种ASPNET功能也并不困难首先你要把一个常规ListBox控件拖动到你的Web表单上并且使用一些数据填充它
然后再添加一个标签用作标题还有一组按钮用作重排序按钮捕获这些按钮的服务器端事件是ASPNET中的标准操作因此你只需要使用一种方法来取得当前选定的项并且根据用户点击的按钮从而把它放到该列表中的更高或更低的位置即可例如你可能编写如下的代码实现移动列表中的一项
i_Index = ListBoxSelectedIndex;
o_Item = ListBoxSelectedItem;
ListBoxItemsRemoveAt(thisSelectedIndex);
i_Index;
if(i_Index < ) i_Index = ;
ListBoxItemsInsert(i_Index o_Item);
下面让我进行简单的分析首先我保存了当前的列表中选定项的索引值与当前项然后我在当前位置删除该项之后在一个较低位置(上一个索引值减)重新插入该项这里的逻辑非常简单那么为什么我还要说明这个问题呢?
借助于这种常规的ASPNET编程方法Web表单上面的重排序按钮将会引发一个实现ListBox中重排序的服务器端事件这是由一个到服务器的回寄触发的因此这个回寄可能是一次繁重的往返具体要信赖于表单上的具体内容及因特网速度
然而因为这一代码实现的是一个标准ASPNET回寄过程所以由ASPNET使用它的ViewState机制来负责状态处理当再次生成页面时列表框内容按要求的顺序正确生成
当然你也可以使用与此相同的常规方式在ListMover控件中重新创建这个功能篇幅所限我在此省略只好留待读者您来实现这个Web表单上包含一对ListBox还有一些指示从左向右或从右向左移动的按钮这些按钮的服务器端事件将从一个ListBox中提取选择的项然后把它添加到另一个列表中反之亦然如在刚才的例子中所展示的ViewState在此能够完好工作以保持这两个ListBox中的项
三 目的
下面我想向你展示如何把刚才描述的这些例子中所用的单个控件放到一个Web表单上你可能猜出我将向你展示如何把这两个例子中的功能封装到它们自己的一个Web控件中借助于与在常规方法示例中描述的相同的服务器端事件模型我们可以把所有的行为封装到每一个控件中来实现必要的功能既然每一个控件都能够控制它自己的状态那么包含它们的Web表单不必要做任何额外的工作
到目前为止一切顺利你可能问问题在哪里?很好假定页面开发者在含有大量内容的页面上使用这两个控件而且每当发生一次重排序或移动都需要到服务器端的重回寄时这显然不是一个高效的Web站点要实现的这正是使用一些JavaScript的原因
在本例中你要使用JavaScript代码来存取EnhancedListBox控件中ListBox的内容以便在客户端进行重排序
在ListMover控件中JavaScript代码将把项从一个列表移动到另一个列表其最终结果是一样的但是不需要进行服务器来回传送因为不需要触发任何回寄这样以来你就可以解决即时响应和不需要回馈的问题
四 问题
在服务器端生成内容与在客户端生成内容之间有明显的界定事实上大部分情况下这两部分没有关系因此问题出现了其实一个Web控件只是一个服务器端组件它负责把HTML生成到浏览器端的确标准ASPNET ListBox控件正是以HTML形式生成一个ListBox(作为一个<select>标签)
在<select>标签中的<option>子标签可以使用ListBox控件中的Item属性的内容来创建Item属性在服务器端被填充而其内容有助于在生成期间构建适当的HTML这非常类似于生成一个<input>标签的文本框Web控件而它的Text属性映射到<input>标签的Value属性每当触发一个到服务器的页面回寄时ListBox控件的Item属性都被保存到ViewState中并且在重新生成页面前从ViewState中进行重建
在EnhancedListBox中进行重排序或在服务器端的ListMover中移动项都非常直接并且允许支持正常的内置的ViewState机制而不需要我们作任何干扰但是当你使用客户端JavaScript添加这一能力来实现它们的功能时它将破坏ViewState这些控件并不再转回到服务器端所以Item集合属性永远不会被保存以便在重新生成时被重载代之的是直接在HTML级别上存取生成的<select>标签中的<option>项你可以借助JavaScript代码移动或重排序控件项但是当在页面上再次发生回寄时你猜发生了什么?在移动(或重排序)开始前控件的列表项就恢复它们的状态
我说过如果功能发生在回寄期间的服务器端那么ViewState被保存并且被良好重载从而使Item集合正确填充但是既然你的最终目标是在客户端实现这个功能那么你就不再需要重新调整Item属性的内容而是由你依赖的这个属性负责状态存储现在你可能会为难了但是别担心—我有一个解决方案现在让我们开始使用必要的客户端脚本代码来开发该控件来实现每一个子控件所需要的功能然后我将向你展示如何使它与服务器代码保持重新同步
五 EnhancedListBox控件
在这个控件中你要把两部分内容添加到现有ASPNET ListBox控件首先添加一个头部—把一个标签放到一个ListBox的上方然后把两个按钮添加到ListBox—分别用于向下和向上重排序
注意 为了简单起见我在后面所有的代码描述中省略所有的属性部分
现在创建一个继承自ListBox控件的新类如下所示
using SystemWebUI;
using SystemWebUIWebControls;
public class EnhancedListBox : ListBox
{}
如果你编译这部分代码并且把该控件添加到你的工具箱中那么你将有一个完整功能的ASPNET ListBox控件副本我把这个控件作为一个继承控件开发是因为我想使它具有一个ASPNET ListBox控件的占位符的作用以后我再添加其它的属性以实现头部的可见性并支持重排序按钮的打开或关闭当这些属性全部关闭时这些控件将在外观与行为上与一个常规ListBox控件一样然而你不能使用一个重载的CreateChildControls把控件添加到其上因为这个函数是用来构建一个控件层次树的这个ASPNET ListBox控件被编写为一个生成控件而且直接把它的所有HTML内容绘制到生成引擎这样以来你需要在此处注入你的内容你将使用生成控件方法来构建一个标签和两个按钮并且通过重载Render方法来生成它们然而一旦你重载这个方法你就完全取消了所有的在原始ListBox中的生成内容而这是不可取的因此我想借助于一些小技巧来实现
六 代码注入
我的方案是以一个标准生成控件方式来绘制这个控件其中包括table标签以及该标签与我添加的按钮的生成方式等当我编写生成显示部分时也就是在我想注入继承的原始的ListBox的地方我调用了baseRender方法这将把微软为ListBox控件编写的所有代码注入到我试图绘制的HTML部分(见源码中的列表)下列表格中列出的属性决定了这部分代码的外观与行为在本文中我没有列出相应的属性代码但是你可以在下载源代码中找到注意在代码中你要生成的按钮将导致一个回寄(基于属性ReorderButtonPostback的值)而对接口IPostBackEventHandler的实现将捕获这个回寄
表格:EnhancedListBox属性
属性名 类别
UPButtonCaption Appearance
DownButtonCaption Appearance
ShowReorderButtons Appearance
ShowHeading Appearance
HeadingCaption Appearance
ReorderButtonPostBack Behavior
SideButtonWidth Layout
SideButtonHeight Layout
SideButtonVerticalAlign Layout
ReorderButtonSide Layout
UpButtonStyle Styles
DownButtonStyle Styles
ListBoxStyle Styles
HeadingStyles Styles
现在既然该控件已经看上去如你希望的样式(见图)那么你可以让该按钮多负责一些工作而不是仅引发一个回寄最终的产品中包含事件处理代码这部分代码位于接口IPostBackEventHandler的实现中这样以来事件能够被有选择地向服务器激发而另一方面开发者也可以在此处加入更多的代码但是记住你要使用这些按钮来重排序ListBox中的项并且希望在不执行回寄的情况下实现这一功能现在我们开始分析最有趣的部分
图EnhancedListBox控件让用户重排序一个列表中的项