摘要在本系列文章中我們首先討論使用 構建一個標准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控件讓用戶重排序一個列表中的項
From:http://tw.wingwit.com/Article/program/net/201311/13273.html