ASP.NET开发复合控件之加强列表框实例
- .Net编程
- 2007-11-23
- 121热度
- 0评论
摘要:作为一个ASP程序员,我们无论如何都会经常需要一些控件支持我们的系统开发,无论是用户控件还是服务端控件,往往我们将页面中的公共部分给抽取出来独立成一个功能单元开发。但是,往往光这样还不够,因为控件功能往往不那么单一,它往往依托一组控件的协调最后实现特定功能。在本系列文章中,我们首先讨论使用ASP.net 2.0构建一个标准ListBox控件的增强版本(EnhancedListBox)。
构建提供丰富的客户端接口的复杂Web控件经常需要把一些客户端JavaScript代码与控件的服务器端代码集成到一起。然而,在一些情况下,为了达到某种巧妙的效果而把问题搞得过于复杂经常会破坏控件的内部服务器代码与生成的客户端HTML代码之间的数据同步,而当进行页面回寄时这将成为一个问题。在本文中,我将首先构建两个“很酷”的Web控件(都极容易导致这一问题),然后向你展示如何来修改这一“脆弱性”。
在本系列文章中,我们首先讨论使用ASP.net 2.0构建一个标准ListBox控件的增强版本(EnhancedListBox)。这个控件能够对它的项进行重排序,并且能够实现客户端与服务器端的同步功能。然后,我们把这样的两个控件组合起来创建一个复合控件(ListMover),运行效果图如下:
作者注:本文假定你对定制Web控件开发有一个基本了解。因此,我将不再重复Web控件开发的基础内容,例如属性工作原理与风格的添加方式。
一、 引言
本文中,我们将使用C#+ASP.NET 2.0来定制这些控件,并在后面向你简短介绍如何使之工作在ASP.NET 1.1(或1.0)环境中。
当前,HTML仍然保持为Web应用程序生成阶段的主要语言。遗憾的是,它所使用的协议是无状态的,所以必须由Web开发者自己来处理这种无状态特点。通过使用一些架构特征,例如回寄机制和ViewState变量,ASP.NET有助于处理这个问题。然而,为了实现某些功能,还需要再作努力,从而借助于回寄事件把Web页面不断向服务器发出请求的各种技术结合起来。
具体地说,我将分析如何使用JavaScript和DHTML存取在客户端生成的元素。其实,把客户端和服务器功能融合到一起要求使用大量的技巧才能达到最佳用户体验效果,而微软在其ASP.NET校验控件中就实现了这一点。为了提供一种丰富的客户端校验效果,该控件中使用了大量的JavaScript。 [NextPage]
二、 一种常规实现方法
下面,我想向你展示如何构建一组很酷的控件,它们具有你在商业控件中才能看到的优秀功能。稍后,我将继续展示定制Web控件带给Web编程的完全封装优点。既然你已经了解如何开发定制Web控件,那么你应该知道的一个概念是封装一个控件所有的功能和行为(就象你在一个标准业务对象中所实现的那样)。在学习构建具有复杂行为的控件时,这种封装将极有用处。
在第一个控件中,我将向你展示如何构建一个称为EnhancedListBox的控件。这个控件将扩展ASP.NET的ListBox控件—添加一个头部和一些重排序按钮。注意,这是一个直接继承自常规ListBox的控件。
之后,我还将向你展示如何构建一个复合控件—ListMover,它将包含两个上面提到的EnhancedListBox控件。这个ListMover控件还包含一些允许你从一个列表到另一个列表中移动项的按钮。
其实,用常规方法(非面向Web控件的)来实现这种ASP.NET功能也并不困难。首先,你要把一个常规ListBox控件拖动到你的Web表单上并且使用一些数据填充它。
然后,再添加一个标签用作标题,还有一组按钮用作重排序按钮。捕获这些按钮的服务器端事件是ASP.NET中的标准操作;因此,你只需要使用一种方法来取得当前选定的项并且根据用户点击的按钮从而把它放到该列表中的更高或更低的位置即可。例如,你可能编写如下的代码实现移动列表中的一项。
i_Index = ListBox1.SelectedIndex;
o_Item = ListBox1.SelectedItem;
ListBox1.Items.RemoveAt(this.SelectedIndex);
i_Index--;
if(i_Index < 0) i_Index = 0;
ListBox1.Items.Insert(i_Index, o_Item);
下面,让我进行简单的分析。首先,我保存了当前的列表中选定项的索引值与当前项。然后,我在当前位置删除该项;之后,在一个较低位置(上一个索引值减1)重新插入该项。这里的逻辑非常简单,那么为什么我还要说明这个问题呢?
借助于这种常规的ASP.NET编程方法,Web表单上面的重排序按钮将会引发一个实现ListBox中重排序的服务器端事件。这是由一个到服务器的回寄触发的;因此,这个回寄可能是一次“繁重的”往返,具体要信赖于表单上的具体内容及因特网速度。
然而,因为这一代码实现的是一个标准ASP.NET回寄过程,所以由ASP.NET使用它的ViewState机制来负责状态处理。当再次生成页面时,列表框内容按要求的顺序正确生成。
当然,你也可以使用与此相同的常规方式在ListMover控件中重新创建这个功能。篇幅所限,我在此省略,只好留待读者您来实现。这个Web表单上包含一对ListBox,还有一些指示从左向右或从右向左移动的按钮。这些按钮的服务器端事件将从一个ListBox中提取选择的项,然后把它添加到另一个列表中;反之亦然。如在刚才的例子中所展示的,ViewState在此能够完好工作以保持这两个ListBox中的项。
三、 目的
下面,我想向你展示如何把刚才描述的这些例子中所用的单个控件放到一个Web表单上。你可能猜出,我将向你展示如何把这两个例子中的功能封装到它们自己的一个Web控件中。借助于与在常规方法示例中描述的相同的服务器端事件模型,我们可以把所有的行为封装到每一个控件中来实现必要的功能。既然每一个控件都能够控制它自己的状态,那么包含它们的Web表单不必要做任何额外的工作。 [NextPage]
到目前为止,一切顺利。你可能问:“问题在哪里?”很好,假定页面开发者在含有大量内容的页面上使用这两个控件,而且每当发生一次重排序或移动,都需要到服务器端的重回寄时,这显然不是一个高效的Web站点要实现的。这正是使用一些JavaScript的原因。
在本例中,你要使用JavaScript代码来存取EnhancedListBox控件中ListBox的内容以便在客户端进行重排序。
在ListMover控件中,JavaScript代码将把项从一个列表移动到另一个列表。其最终结果是一样的,但是不需要进行服务器来回传送,因为不需要触发任何回寄。这样以来,你就可以解决即时响应和不需要回馈的问题。
四、 问题
ASP.net在服务器端生成内容与在客户端生成内容之间有明显的界定。事实上,大部分情况下,这两部分没有关系;因此,问题出现了。其实,一个Web控件只是一个服务器端组件,它负责把HTML生成到浏览器端。的确,标准ASP.NET 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代码移动或重排序控件项;但是,当在页面上再次发生回寄时,你猜发生了什么?在移动(或重排序)开始前,控件的列表项就恢复它们的状态。 [NextPage]
我说过,如果功能发生在回寄期间的服务器端,那么,ViewState被保存并且被良好重载,从而使Item集合正确填充。但是,既然你的最终目标是在客户端实现这个功能,那么你就不再需要重新调整Item属性的内容,而是由你依赖的这个属性负责状态存储。现在,你可能会为难了。但是别担心—我有一个解决方案。现在,让我们开始使用必要的客户端脚本代码来开发该控件来实现每一个子控件所需要的功能。然后,我将向你展示如何使它与服务器代码保持重新同步。
五、 EnhancedListBox控件
在这个控件中,你要把两部分内容添加到现有ASP.NET ListBox控件。首先,添加一个头部—把一个标签放到一个ListBox的上方。然后,把两个按钮添加到ListBox—分别用于向下和向上重排序。
注意 为了简单起见,我在后面所有的代码描述中省略所有的属性部分。
现在,创建一个继承自ListBox控件的新类,如下所示:
using System.Web.UI;
using System.Web.UI.WebControls;
public class EnhancedListBox : ListBox
{}
如果你编译这部分代码并且把该控件添加到你的工具箱中,那么你将有一个完整功能的ASP.NET ListBox控件副本。我把这个控件作为一个继承控件开发,是因为我想使它具有一个ASP.NET ListBox控件的“占位符”的作用。以后,我再添加其它的属性以实现头部的可见性并支持重排序按钮的打开或关闭。当这些属性全部关闭时,这些控件将在外观与行为上与一个常规ListBox控件一样。然而,你不能使用一个重载的CreateChildControls把控件添加到其上,因为这个函数是用来构建一个控件层次树的。
这个ASP.NET ListBox控件被编写为一个生成控件而且直接把它的所有HTML内容绘制到生成引擎;这样以来,你需要在此处“注入”你的内容。你将使用生成控件方法来构建一个标签和两个按钮,并且通过重载Render方法来生成它们。然而,一旦你重载这个方法,你就完全取消了所有的在原始ListBox中的生成内容,而这是不可取的。因此,我想借助于一些小技巧来实现。[NextPage]
摘要:作为一个ASP程序员,我们无论如何都会经常需要一些控件支持我们的系统开发,无论是用户控件还是服务端控件,往往我们将页面中的公共部分给抽取出来独立成一个功能单元开发。但是,往往光这样还不够,因为控件功能往往不那么单一,它往往依托一组控件的协调最后实现特定功能。在本系列文章中,我们首先讨论使用ASP.net 2.0构建一个标准ListBox控件的增强版本(EnhancedListBox)。
构建提供丰富的客户端接口的复杂Web控件经常需要把一些客户端JavaScript代码与控件的服务器端代码集成到一起。然而,在一些情况下,为了达到某种巧妙的效果而把问题搞得过于复杂经常会破坏控件的内部服务器代码与生成的客户端HTML代码之间的数据同步,而当进行页面回寄时这将成为一个问题。在本文中,我将首先构建两个“很酷”的Web控件(都极容易导致这一问题),然后向你展示如何来修改这一“脆弱性”。
在本系列文章中,我们首先讨论使用ASP.net 2.0构建一个标准ListBox控件的增强版本(EnhancedListBox)。这个控件能够对它的项进行重排序,并且能够实现客户端与服务器端的同步功能。然后,我们把这样的两个控件组合起来创建一个复合控件(ListMover),运行效果图如下:
作者注:本文假定你对定制Web控件开发有一个基本了解。因此,我将不再重复Web控件开发的基础内容,例如属性工作原理与风格的添加方式。
一、 引言
本文中,我们将使用C#+ASP.NET 2.0来定制这些控件,并在后面向你简短介绍如何使之工作在ASP.NET 1.1(或1.0)环境中。
当前,HTML仍然保持为Web应用程序生成阶段的主要语言。遗憾的是,它所使用的协议是无状态的,所以必须由Web开发者自己来处理这种无状态特点。通过使用一些架构特征,例如回寄机制和ViewState变量,ASP.NET有助于处理这个问题。然而,为了实现某些功能,还需要再作努力,从而借助于回寄事件把Web页面不断向服务器发出请求的各种技术结合起来。
具体地说,我将分析如何使用JavaScript和DHTML存取在客户端生成的元素。其实,把客户端和服务器功能融合到一起要求使用大量的技巧才能达到最佳用户体验效果,而微软在其ASP.NET校验控件中就实现了这一点。为了提供一种丰富的客户端校验效果,该控件中使用了大量的JavaScript。
[NextPage]
为了使用客户端代码实现EnhancedListBox中项的重排序,你必须使用JavaScript脚本,并且要把它们依附到EnhancedListBox的两个按钮上。为此,我建议你使用“往后考虑”的方法。就象编写一个老式的ASP以前的Web页面,首先编写一些生成HTML文件的JavaScript。为此,最好的方法是运行该控件,然后观察其源码并把它的HTML代码复制到一个编辑器,再添加JavaScript。列表2(见下载源代码)展示了你需要添加到你的控件中的JavaScript的原始形式。然后,借助于StringBuilder/StringWriter技术(参考源码列表3),该控件构建这部分代码。该JavaScript代码由两部分功能组成:接收一个HTML控件(在本例中是一个<select>控件);使用选择索引并且在列表中上下移动它(基本上与我在本文开始我使用服务器代码向你展示的一样)。现在,你要理解,你把该JavaScript代码添加到Web控件的何处。为了实现在一个Web表单上有多个EnhancedListBox控件的情况下,该JavaScript代码不会被重复复制,你需要使用Page.ClientScript对象的ReGISterClientScriptBlock方法输出它。
要使这个方法起作用,你必须在重载的OnInit事件中调用它(见源码中列表4)。
最后,为使按钮正确工作,你需要把添加的客户端方法依附到其上。在列表1中的代码中,你会看到引用了一个方法RenderButtons。尽管我没有把该代码在此列出(请参考本文相应源码),但是它能够使用我在以前文章中介绍的技术生成按钮。当时,在生成实际HTML标签的之前,标签属性是使用AddAttribute方法以栈式存放的。在此,你使用一样的技术把客户端方法依附到你的按钮。
string s_MoveUp = "MoveItemUp(document.all." +this.ClientID + ");
output.AddAttribute(HtmlTextWriterAttribute.OnClick,s_MoveUp);
记住,MoveItemUp是你已经编写成功的JavaScript函数之一。在生成用于排序的按钮之前,该代码将以堆栈存放这些JavaScript命令。对于向下(down)按钮,你使用一样的技术。注意,我使用ClientId代表该生成后的控件的ID;但是,在这个控件位于一个复合控件内部时,这个属性要考虑使用父控件的名字。 [NextPage]
现在,你可以成功地把该控件应用于一个Web表单中。你可以使用与你操作一个标准ListBox控件一样的方式在其上添加一些项。
事实上,这完全是一个投放位置占位符(或ASP.NET ListBox控件)。当你使用重排序按钮时,你将看到列表中的项相应地改变顺序。现在让我们先记下这个问题。如果你把一个按钮拖动到一个Web表单上(不需要为之添加代码)并执行一个回寄,你猜会发生什么呢?完全与我以前描述的一样;任何你使用重排序按钮作的重排序改变都将恢复到在最近一次回寄之前该控件看上去的状态。因此,让我们修改一下这个问题。
首先,我再添加一些JavaScript(源码列表5)。注意,这部分代码被添加到重载的OnInit方法中并且使用StringBuilder/StringWriter技术进行构建;而且,这个JavaScript方法的名字是BuildItemList。这个函数负责构建列表框完整内容的一个字符串描述并且把该串放到要传递到该函数的一个HTML元素的value属性中。你可以把这看作是列表内容的一种串行化。该串行化的输出风格会根据你自己的设计的不同而有所不同。调用这个JavaScript函数需要依附到该按钮上的其它代码。
string s_MoveUp = "MoveItemUp(document.all." + this.ClientID + "); ";
string s_BuildItemList ="BuildItemList(document.all." + this.ClientID +
",document.all.__" + this.ClientID + "); ";
output.AddAttribute(HtmlTextWriterAttribute.Onclick,MoveUp + " " + BuildItemList);
现在,让我们来分析一下你发送到BuildItemList函数的两个参数。第一个参数相应于生成的控件(<select>标签)的ID。第二个参数是另外一个ID,与前一个命名一致,但是前面有一个"__"。这是一个你仍然需要添加到你的Web控件的隐藏的文本框,它将作为一个“串行化”项列表的占位符。我要在OnPreRender事件中注册这个隐藏的文本域。
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if(Page != null)
{
Page.ClientScript.RegisterHiddenField("__" + this.ID, "");
}
}
注意,我已经使用我们的控件的ID来标识隐藏的文本域。
到目前为止,你已经拥有了一个完整功能的Web控件;其中,客户端JavaScript被绑定到其中的两个按钮上。该JavaScript成功地实现在ListBox中的项的重排序并且把其内容串行化为一个字符串;然后,该字符串被存储在一个隐藏的文本域中。所有这些都发生在客户端。如果一个回寄发生,不会发生重排序,因为当重排序时控件的Item服务器属性还没有收到你对它作的任何改变的消息;但是幸运的是,位于隐藏的文本域中的表单的一个串行化快照中发生了这一变化。现在,你有了可以与Item属性一起使用的内容了。那么,接下来,你该如何实现呢?
同步
为了在第一次回寄和所有随后的回寄中实现同步,ASP.net在IPostBackDataHandler接口的实现中提供了一个LoadPostData方法。在每一次回寄时都要调用这个LoadPostData方法;因此,你需要在此做一些工作。 [NextPage]
值得一提的是,ASP.NET 2.0修整了一个在1.1版本中被忽视的小地方,然而这一修改能够使你的工作容易许多。ASP.NET ListBox控件已经在两个版本(1.1和2.0)中实现了IPostBackDataHandler接口。但是在2.0版本中,微软使这个接口的方法定义虚拟化(virtual,在VB中称作Overridable)。这意味着,你不必在EnhancedListBox控件中重新实现这个接口;而是,你仅需重载LoadPostData方法。
更重要的是,这也意味着,你可以存取基类实现而不必创建所有已经存在于你的扩展控件中的功能。什么功能呢?这包括微软加于其中的一切:用于处理Item集合,SelectedIndex,SelectedValue和SelectedItem属性,及其它许多执行ListBox控件功能的代码。在ASP.NET 1.1中,你必须在你的派生控件中实现这个接口并且要提供你自己对这两个方法的定义代码,不仅包括你自己的加入的代码而且还要重复微软已经在其控制中所实现的一切。
我猜测,微软有人已经发现了他们的实现中的错误,并且把方法变为virtual的,这样开发者能够存取基类的代码。因此,在源码列表6中向你展示如何实现重载的LoadPostData方法。在这个重载中,你将首先调用基类实现代码;然后,加上你需要的代码以与Item集合同步。
另外,你还可以利用ListBox控件—通过把它编写成一个复合控件。此时,你需要把ListBox中的每一个属性映射到你的EnhancedListBox以便使它成为ListBox控件的一个投放位置点位符。无论使用哪一种方法,或者通过LoadPostData方法的重新创建,你都仍然需要写很多代码。如果我专门为ASP.NET1.1编写这个控件,那么我很可能采取最直接的方案:复合控件方案。
LoadPostData方法使你能够存取寄送到服务器的每一个域,包括你的隐藏文本域(存储在要传递到这个方法的postCollection参数中)。你可以问:为什么需要该隐藏文本域,而不是使用这个参数来存取被回寄的<select>元素呢?现在,我作一下解释。首先,回顾一下典型的ASP时代,当时你使用Request.Form属性来存取页面域。在回寄时,你能够存取一个<select>元素的唯一的部分是选择的项。在该方案中,你需要完整的列表内容(因此,包括隐藏的文本域)。列表6向你展示如何分析该隐藏的文本域的内容并且把Item重新添加到Item集合中。注意,你是怎样调用基类实现的。
最后,在你第一次生成控件时,你必须构建这个隐藏的文本域,以防在任何重排序前发生页面回寄。Render方法的最后一行是:
output.Write("<script language='JavaScript'>BuildItemList(document.all." + this.ClientID +",document.all.__" + this.ClientID +");</script>");
你可以在列表1的最后看到这一点。
现在,你可以使用EnhancedListBox控件来重排序一些项,回寄,并且确保在重新生成页面前,控件的服务器存储与在客户端被改变的客户端存储完全同步。因此,现在让我们使用相同的技术来构建一个复合控件ListMover。
构建复合控件—ListMover
这个ListMover控件包含两个EnhancedListBox控件,还有一些按钮用于在两个列表之间来回移动项。借助于这些复合控件构建技术,你可以学习如何创建子控⑶沂褂靡恍〩TML生成它们,最终的控件看上去如图2所示。对于这个控件,你要注意的是某些事情必须发生的位置。
这个ListMover控件提供了一种标准方式让用户在两个列表间移动项。
首先,借助于与在以前的控件中相同的技术,你必须把在这个控件中需要的JavaScript代码添加到OnInit事件的重载版本中。列表7显示了你需要的JavaScript代码。如你在上一个控件中所做的一样,你也是使用JavaScript存取一个ListBox(<select>元素)中的元素。而且,我已经编制了函数分别实现把项添加到一个列表,从一个列表中删除项,以及从一个列表中添加或删除所有项。 [NextPage]
我已经进行了功能的分离,而不是创建单个“move”方法;这样以来,我可以实现基于属性设置而使得从一个列表中删除项成为可选的。毫无疑问,这可以使最终的控件更为强壮些,但是我在本文中不再分析这些代码。还应该注意,就象在前面控件中一样,我也添加了一个BuildItemList方法。
现在,你需要把这一客户端代码依附到复合控件的按钮中。你可以在CreateChildControls方法的最后完成这一点;并且,在此时,完成子控件的初始化和构建控件集合。在此,我仅向你展示相应于一个按钮的代码(另外的按钮代码与此类似,省略)。
string s_AddToLeft = "AddSelectedItemToList(document.all." +
this.lstItemsOnRight.ClientID + ", document.all."
+ this.lstItemsOnLeft.ClientID + ", " +
(this.AllowDuplicatesOnLeft ? "true" : "false") + "); ";
string s_RemoveFromRight = "RemoveSelectedItemFromList(document.all." +
this.lstItemsOnRight.ClientID + "); ";
string s_BuildItemList = "BuildItemList(document.all." +
this.lstItemsOnRight.ClientID + ", document.all.__" + lstItemsOnRight.ClientID + "); " + "BuildItemList(document.all." + this.lstItemsOnLeft.ClientID + ", document.all.__"
+ lstItemsOnLeft.ClientID + "); ";
this.btnAdd.Attributes.Add("onclick", s_AddToLeft
+ " " + s_RemoveFromRight + " " + s_BuildItemList
+ " return false");
注意,我实现了在以前的控件中同样的工作。我把JavaScript函数调用构建成一字符串并且把它们依附到一个按钮上。主要区别在于,既然这是一个包含其它控件的复合控件,那么你可能使用把代码添加到onclick事件的Attributes.Add方法,这与在一个生成控件中把它放到一个栈上的方法形成对照。还要注意,我把多个功能放到onclick属性中;而且,函数调用的最后返回false以便取消按钮将执行的任何回寄。
最后,代码将在一个对Render重载的方法中初始化对客户端函数BuildItemList的调用。这看起来很象我在EnhancedListBox控件中向你介绍的那个,在此不再重复。注意,在这个控件中,我注册了两个隐藏的文本域,每一个相应于一个ListBox。
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if(Page != null)
{
Page.ClientScript.ReGISterHiddenField("__" + this.lstItemsOnRight.ClientID,
"");
Page.ClientScript.RegisterHiddenField("__" + this.lstItemsOnLeft.ClientID,
"");
Page.RegisterRequiresPostBack(this);
}
}
现在,你已经构建成功该复合控件,能够提供一些客户端JavaScript,并且把它绑定到按钮上。与以前一样,你可以把它放到一个表单上并且使用它;但是,在你添加同步代码之前,它仍将会遇到你在第一个控件中所遇到的问题—你可以前后移动项,但是一旦你初始化一个回寄(通过表单上的任何其它控件),该控件就会恢复到它回寄之前的状态。
为了修改这个问题,你要实现你在第一个控件中所做的同样的工作。然而,既然你在开发一个复合控件,而不是扩展一个已经现有的控件,那么你需要实现IPostBackDataHandler接口并且提供LoadPostData和RaisePostDataChangedEvent方法的实现代码。这些实现(见列表7)与前面的控件基本一致,除了你要实现两个EnhancedListBox控件中的项集合的同步而不是只考虑一个控件外。并且与以前一样,你需要确保你保存你的SelectedIndex位置;这样以来,在你完成项集合的同步后你就可以把它们设置回去。还要注意,在第一个控件中,你重载了基控件的LoadPostData方法,因此在某处调用了它的基类。现在,既然你要从头编写一个复合控件,那么就没有基类可调用,而仅需提供你自己的方法实现。
这个控件的最后版本包含若干新的属性:包括用来决定是否添加到一个列表中的项能够被从另一个列表中删除的属性(如果一个列表将允许出现重复项的话);它还包含可扩展的风格化以实现最大化重用的目的,等等。
就这些。你已经使用了可用于客户端脚本中的隐藏的文本域来存储列表框的状态。在回寄期间,你使用隐藏文本域的内容来与服务器端项集合重新同步。最终结果是一个漂亮的复合控件—允许你在没有服务器回寄的情况下实现各列表项间的来回移动,而当一个回寄真正发生时仍能够保持这种变化。
取二者最优
前面我没有提及的一个细节是为什么我在本文中混合了两个控件。文章一开始,我首先向你展示了一个标准ListBox控件的增强版本,然后把这个增强控件的两个实例应用于ListMover控件,而没有使用两个标准ListBox控件来构建这个ListMover。在本文中,我没有涉及的是ListMover控件的属性部分,它们将负责映射添加到EnhancedListBox控件上的属性。通过这种方式,我就能够从包含两个EnhancedListBox控件的ListMover控件中控制两个EnhancedListBox控件的增强功能。因此,你可以看到,你拥有结合了两个控件的最好的功能—你有了一个ListMover控件,它允许你在两个列表或单个列表的各项之间进行项的移动与重排序。
其实,这里真正关键的地方在于面向Web控件的ASP.net开发—完全封装。本文中的EnhancedListBox控件包含实现其目标(对它的项进行重排序)的所有代码。当我把两个这种控件包括在一个ListMover控件中时,我可以使用所有伴随着它们的智能性作为新控件的额外功能,包括每一个控件含有的客户端脚本以及在EnhancedListBox控件中的客户端到服务器的同步功能。因此,这个ListMover控件只需注意其自己的功能。图3展示了和EnhancedListBox控件在一起的ListMover控件,其中重排序按钮处于开状态。