asp.net

位置:IT落伍者 >> asp.net >> 浏览文章

C#+ASP.NET 2.0 定制复合组件之高级篇


发布日期:2019年04月01日
 
C#+ASP.NET 2.0 定制复合组件之高级篇

增加EnhancedListBox的客户端功能

为了使用客户端代码实现EnhancedListBox中项的重排序你必须使用JavaScript脚本并且要把它们依附到EnhancedListBox的两个按钮上为此我建议你使用往后考虑的方法就象编写一个老式的ASP以前的Web页面首先编写一些生成HTML文件的JavaScript为此最好的方法是运行该控件然后观察其源码并把它的HTML代码复制到一个编辑器再添加JavaScript列表(见下载源代码)展示了你需要添加到你的控件中的JavaScript的原始形式然后借助于StringBuilder/StringWriter技术(参考源码列表)该控件构建这部分代码该JavaScript代码由两部分功能组成接收一个HTML控件(在本例中是一个<select>控件)使用选择索引并且在列表中上下移动它(基本上与我在本文开始我使用服务器代码向你展示的一样)现在你要理解你把该JavaScript代码添加到Web控件的何处为了实现在一个Web表单上有多个EnhancedListBox控件的情况下该JavaScript代码不会被重复复制你需要使用PageClientScript对象的ReGISterClientScriptBlock方法输出它

要使这个方法起作用你必须在重载的OnInit事件中调用它(见源码中列表)

最后为使按钮正确工作你需要把添加的客户端方法依附到其上在列表中的代码中你会看到引用了一个方法RenderButtons尽管我没有把该代码在此列出(请参考本文相应源码)但是它能够使用我在以前文章中介绍的技术生成按钮当时在生成实际HTML标签的之前标签属性是使用AddAttribute方法以栈式存放的在此你使用一样的技术把客户端方法依附到你的按钮

string s_MoveUp = MoveItemUp(documentall +thisClientID + );

outputAddAttribute(HtmlTextWriterAttributeOnClicks_MoveUp);

记住MoveItemUp是你已经编写成功的JavaScript函数之一在生成用于排序的按钮之前该代码将以堆栈存放这些JavaScript命令对于向下(down)按钮你使用一样的技术注意我使用ClientId代表该生成后的控件的ID但是在这个控件位于一个复合控件内部时这个属性要考虑使用父控件的名字

现在你可以成功地把该控件应用于一个Web表单中你可以使用与你操作一个标准ListBox控件一样的方式在其上添加一些项

事实上这完全是一个投放位置占位符(或ASPNET ListBox控件)当你使用重排序按钮时你将看到列表中的项相应地改变顺序现在让我们先记下这个问题如果你把一个按钮拖动到一个Web表单上(不需要为之添加代码)并执行一个回寄你猜会发生什么呢?完全与我以前描述的一样任何你使用重排序按钮作的重排序改变都将恢复到在最近一次回寄之前该控件看上去的状态因此让我们修改一下这个问题

首先我再添加一些JavaScript(源码列表注意这部分代码被添加到重载的OnInit方法中并且使用StringBuilder/StringWriter技术进行构建而且这个JavaScript方法的名字是BuildItemList这个函数负责构建列表框完整内容的一个字符串描述并且把该串放到要传递到该函数的一个HTML元素的value属性中你可以把这看作是列表内容的一种串行化该串行化的输出风格会根据你自己的设计的不同而有所不同调用这个JavaScript函数需要依附到该按钮上的其它代码

string s_MoveUp = MoveItemUp(documentall + thisClientID + ); ;

string s_BuildItemList =BuildItemList(documentall + thisClientID +

documentall__ + thisClientID + ); ;

outputAddAttribute(HtmlTextWriterAttributeOnclickMoveUp + + BuildItemList);

现在让我们来分析一下你发送到BuildItemList函数的两个参数第一个参数相应于生成的控件(<select>标签)的ID第二个参数是另外一个ID与前一个命名一致但是前面有一个__这是一个你仍然需要添加到你的Web控件的隐藏的文本框它将作为一个串行化项列表的占位符我要在OnPreRender事件中注册这个隐藏的文本域

protected override void OnPreRender(EventArgs e)

{

baseOnPreRender(e);

if(Page != null)

{

PageClientScriptRegisterHiddenField(__ + thisID );

}

}

注意我已经使用我们的控件的ID来标识隐藏的文本域

到目前为止你已经拥有了一个完整功能的Web控件其中客户端JavaScript被绑定到其中的两个按钮上该JavaScript成功地实现在ListBox中的项的重排序并且把其内容串行化为一个字符串然后该字符串被存储在一个隐藏的文本域中所有这些都发生在客户端如果一个回寄发生不会发生重排序因为当重排序时控件的Item服务器属性还没有收到你对它作的任何改变的消息但是幸运的是位于隐藏的文本域中的表单的一个串行化快照中发生了这一变化现在你有了可以与Item属性一起使用的内容了那么接下来你该如何实现呢?

同步

为了在第一次回寄和所有随后的回寄中实现同步在IPostBackDataHandler接口的实现中提供了一个LoadPostData方法在每一次回寄时都要调用这个LoadPostData方法因此你需要在此做一些工作

值得一提的是ASPNET 修整了一个在版本中被忽视的小地方然而这一修改能够使你的工作容易许多ASPNET ListBox控件已经在两个版本()中实现了IPostBackDataHandler接口但是在版本中微软使这个接口的方法定义虚拟化(virtual在VB中称作Overridable)这意味着你不必在EnhancedListBox控件中重新实现这个接口而是你仅需重载LoadPostData方法

更重要的是这也意味着你可以存取基类实现而不必创建所有已经存在于你的扩展控件中的功能什么功能呢?这包括微软加于其中的一切用于处理Item集合SelectedIndexSelectedValue和SelectedItem属性及其它许多执行ListBox控件功能的代码在ASPNET 你必须在你的派生控件中实现这个接口并且要提供你自己对这两个方法的定义代码不仅包括你自己的加入的代码而且还要重复微软已经在其控制中所实现的一切

我猜测微软有人已经发现了他们的实现中的错误并且把方法变为virtual的这样开发者能够存取基类的代码因此在源码列表中向你展示如何实现重载的LoadPostData方法在这个重载中你将首先调用基类实现代码然后加上你需要的代码以与Item集合同步

另外你还可以利用ListBox控件—通过把它编写成一个复合控件此时你需要把ListBox中的每一个属性映射到你的EnhancedListBox以便使它成为ListBox控件的一个投放位置点位符无论使用哪一种方法或者通过LoadPostData方法的重新创建你都仍然需要写很多代码如果我专门为ASPNET编写这个控件那么我很可能采取最直接的方案复合控件方案

LoadPostData方法使你能够存取寄送到服务器的每一个域包括你的隐藏文本域(存储在要传递到这个方法的postCollection参数中)你可以问为什么需要该隐藏文本域而不是使用这个参数来存取被回寄的<select>元素呢?现在我作一下解释首先回顾一下典型的ASP时代当时你使用RequestForm属性来存取页面域在回寄时你能够存取一个<select>元素的唯一的部分是选择的项在该方案中你需要完整的列表内容(因此包括隐藏的文本域)列表向你展示如何分析该隐藏的文本域的内容并且把Item重新添加到Item集合中注意你是怎样调用基类实现的

最后在你第一次生成控件时你必须构建这个隐藏的文本域以防在任何重排序前发生页面回寄Render方法的最后一行是

outputWrite(<script language=JavaScript>BuildItemList(documentall + thisClientID +documentall__ + thisClientID +);</script>);

你可以在列表的最后看到这一点

现在你可以使用EnhancedListBox控件来重排序一些项回寄并且确保在重新生成页面前控件的服务器存储与在客户端被改变的客户端存储完全同步因此现在让我们使用相同的技术来构建一个复合控件ListMover

构建复合控件—ListMover

这个ListMover控件包含两个EnhancedListBox控件还有一些按钮用于在两个列表之间来回移动项借助于这些复合控件构建技术你可以学习如何创建子控⑶沂褂靡恍〩TML生成它们最终的控件看上去如图所示对于这个控件你要注意的是某些事情必须发生的位置

这个ListMover控件提供了一种标准方式让用户在两个列表间移动项

首先借助于与在以前的控件中相同的技术你必须把在这个控件中需要的JavaScript代码添加到OnInit事件的重载版本中列表显示了你需要的JavaScript代码如你在上一个控件中所做的一样你也是使用JavaScript存取一个ListBox(<select>元素)中的元素而且我已经编制了函数分别实现把项添加到一个列表从一个列表中删除项以及从一个列表中添加或删除所有项

我已经进行了功能的分离而不是创建单个move方法这样以来我可以实现基于属性设置而使得从一个列表中删除项成为可选的毫无疑问这可以使最终的控件更为强壮些但是我在本文中不再分析这些代码还应该注意就象在前面控件中一样我也添加了一个BuildItemList方法

现在你需要把这一客户端代码依附到复合控件的按钮中你可以在CreateChildControls方法的最后完成这一点并且在此时完成子控件的初始化和构建控件集合在此我仅向你展示相应于一个按钮的代码(另外的按钮代码与此类似省略)

string s_AddToLeft = AddSelectedItemToList(documentall +

thislstItemsOnRightClientID + documentall

+ thislstItemsOnLeftClientID + +

(thisAllowDuplicatesOnLeft ? true : false) + ); ;

string s_RemoveFromRight = RemoveSelectedItemFromList(documentall +

thislstItemsOnRightClientID + ); ;

string s_BuildItemList = BuildItemList(documentall +

thislstItemsOnRightClientID + documentall__ + lstItemsOnRightClientID + ); + BuildItemList(documentall + thislstItemsOnLeftClientID + documentall__

+ lstItemsOnLeftClientID + ); ;

thisbtnAddAttributesAdd(onclick s_AddToLeft

+ + s_RemoveFromRight + + s_BuildItemList

+ return false);

注意我实现了在以前的控件中同样的工作我把JavaScript函数调用构建成一字符串并且把它们依附到一个按钮上主要区别在于既然这是一个包含其它控件的复合控件那么你可能使用把代码添加到onclick事件的AttributesAdd方法这与在一个生成控件中把它放到一个栈上的方法形成对照还要注意我把多个功能放到onclick属性中而且函数调用的最后返回false以便取消按钮将执行的任何回寄

最后代码将在一个对Render重载的方法中初始化对客户端函数BuildItemList的调用这看起来很象我在EnhancedListBox控件中向你介绍的那个在此不再重复注意在这个控件中我注册了两个隐藏的文本域每一个相应于一个ListBox

protected override void OnPreRender(EventArgs e)

{

baseOnPreRender(e);

if(Page != null)

{

PageClientScriptReGISterHiddenField(__ + thislstItemsOnRightClientID

);

PageClientScriptRegisterHiddenField(__ + thislstItemsOnLeftClientID

);

PageRegisterRequiresPostBack(this);

}

}

现在你已经构建成功该复合控件能够提供一些客户端JavaScript并且把它绑定到按钮上与以前一样你可以把它放到一个表单上并且使用它但是在你添加同步代码之前它仍将会遇到你在第一个控件中所遇到的问题—你可以前后移动项但是一旦你初始化一个回寄(通过表单上的任何其它控件)该控件就会恢复到它回寄之前的状态

为了修改这个问题你要实现你在第一个控件中所做的同样的工作然而既然你在开发一个复合控件而不是扩展一个已经现有的控件那么你需要实现IPostBackDataHandler接口并且提供LoadPostData和RaisePostDataChangedEvent方法的实现代码这些实现(见列表)与前面的控件基本一致除了你要实现两个EnhancedListBox控件中的项集合的同步而不是只考虑一个控件外并且与以前一样你需要确保你保存你的SelectedIndex位置这样以来在你完成项集合的同步后你就可以把它们设置回去还要注意在第一个控件中你重载了基控件的LoadPostData方法因此在某处调用了它的基类现在既然你要从头编写一个复合控件那么就没有基类可调用而仅需提供你自己的方法实现

这个控件的最后版本包含若干新的属性包括用来决定是否添加到一个列表中的项能够被从另一个列表中删除的属性(如果一个列表将允许出现重复项的话)它还包含可扩展的风格化以实现最大化重用的目的等等

就这些你已经使用了可用于客户端脚本中的隐藏的文本域来存储列表框的状态在回寄期间你使用隐藏文本域的内容来与服务器端项集合重新同步最终结果是一个漂亮的复合控件—允许你在没有服务器回寄的情况下实现各列表项间的来回移动而当一个回寄真正发生时仍能够保持这种变化

取二者最优

前面我没有提及的一个细节是为什么我在本文中混合了两个控件文章一开始我首先向你展示了一个标准ListBox控件的增强版本然后把这个增强控件的两个实例应用于ListMover控件而没有使用两个标准ListBox控件来构建这个ListMover在本文中我没有涉及的是ListMover控件的属性部分它们将负责映射添加到EnhancedListBox控件上的属性通过这种方式我就能够从包含两个EnhancedListBox控件的ListMover控件中控制两个EnhancedListBox控件的增强功能因此你可以看到你拥有结合了两个控件的最好的功能—你有了一个ListMover控件它允许你在两个列表或单个列表的各项之间进行项的移动与重排序

其实这里真正关键的地方在于面向Web控件的开发—完全封装本文中的EnhancedListBox控件包含实现其目标(对它的项进行重排序)的所有代码当我把两个这种控件包括在一个ListMover控件中时我可以使用所有伴随着它们的智能性作为新控件的额外功能包括每一个控件含有的客户端脚本以及在EnhancedListBox控件中的客户端到服务器的同步功能因此这个ListMover控件只需注意其自己的功能展示了和EnhancedListBox控件在一起的ListMover控件其中重排序按钮处于开状态

上一篇:ASP.NET 2.0中XSLT的使用

下一篇:ASP.NET中的session存储模式运用