[翻譯]asp.net 1.x/2.0中的高級列表控件

原文地址:http://aspalliance.com/1071_Smart_ListControl_in_ASPNET_1x20
[原文×××]
[譯者改後×××] 


[翻譯]asp.net 1.x/2.0中的高級列表控件


原文發佈日期:2006.11.16
做者: Bilal Haidar  
翻譯: webabcd


摘要
本文中Bilal Haidar將告訴你如何在asp.net 1.x/2.0下開發一個高級列表控件,使它能夠在客戶端保存其自身每一項的變化。


文章內容

介紹  
在 asp.net 中,當aspx頁上的一個列表控件發生回發事件到服務端時,這個列表控件的功能以下。

不管是依靠WebForm中的Forms仍是QueryString方式傳值,列表控件中的各個項都不會回發至服務端。

只有列表控件中被選中的值纔會在一個回發事件裏把值傳遞到服務端,這說明一個被選中的項能夠在服務端被偵測到。

當這個響應返回到客戶端時,列表控件中的各個項會從視圖狀態檢索到。

以上這個事實暴露出了一些列問題,特別是當你在客戶端做一些變化的時候。好比,在列表控件中刪除或新增項。伴隨着頁面回發至服務端,列表控件在客戶端所作的這些改變都不會被服務端偵測到。不管你作過什麼改變,再一次回發以後,都會消失不見,列表控件又會從新從視圖狀態中加載各個項。

在一些程序中會有對列表控件添加項和刪除項的要求。想象一下,在頁面上有兩個列表控件,而後又一些按鈕可讓兩個列表控件互相移動各自的項。舉個例子,有兩個ListBox分別放置已完成和未完成的任務,當一個任務完成時,你能夠把這個任務從未完成的ListBox中轉移到已完成的ListBox中,可是稍後你會發現,你沒有把那個已完成的任務從未完成的ListBox轉移出來。

爲了讓你的webform有一個更好的表現,好比像實現上面所說的功能,咱們須要用一些客戶端的javascript來代替某些服務端的操做。可是,做爲一個列表控件,ListBox將會有沒法保存其各個項變化的問題,由於這是在asp.net裏ListBox和任何其餘的列表控件的默認的行爲。

本文的用意就是解決這個問題,使列表控件的各個項的變化能夠被保存,能夠被服務端偵測到。

咱們用ListBox控件作例子,在aps.net裏的其餘列表控件也均可以使用一樣的技術使其轉化爲高級列表控件。


解決方案
這部分咱們將在一個實際的方案中去解決問題。咱們須要在新的控件中加入兩個隱藏字段。

第一個隱藏字段的命名規則以下:
ListBoxID + "_ADDED"
上面這個字段將用來保存ListBox控件的增長項。
假設要增長項:
Text = "First Item"
Value = "1"
一個新項將被增長到這個隱藏字段中:
Text|Vale
例,上面的項增長後該隱藏字段則增長:
First Item|1
任意增長項都將被加到這個隱藏字段中
Item1|Value1,Item2|Value2
每一項以何種格式添加到隱藏字段中是有很是嚴格的要求的。正像你從上面看到的那樣,每一項都要用「,」分隔開,如此就能把每個單獨的項做爲一個新的ListItem添加到ListBox控件中。

第二個隱藏字段的命名規則以下:
ListBoxID + "_REMOVED"
上面這個字段將用來保存ListBox控件的刪除項的value。稍後咱們將在服務端看到ListBox控件被選中的值和被刪除的值,從而反應出客戶端的行爲和變化。

除了上面所說起的解決方案的一部分,咱們還將實現IPostBackDataHandler接口,這個接口有兩個方法,LoadPostData和RaisePostDataChangedEvent。本文將對這個接口的實現作簡要論述。


類的實現
當咱們要改善ListBox控件以後,首先要作的就是要繼承ListBox類。目標是給這個類增長實現客戶端各個項變化的功能。

下面的代碼說明了如何繼承ListBox類。

列表1
public class xListBox : ListBox
{
}
ListBox控件全部的公共屬性和方法都將能夠像之前同樣被使用。有一個要引發注意的方法,就是OnPreRender,這個方法將被override以增長前面所說的兩個隱藏字段。

OnPreRender以下:

列表2
protected override void OnPreRender(EventArgs e)
{
     base.OnPreRender(e);
    
     // 將控件註冊爲要求在頁回發至服務器時進行回發處理的控件。
     if (Page != null)
        Page.RegisterRequiresPostBack( this);
    
     // 註冊保存增長項的隱藏字段
    Page.RegisterHiddenField( this.HFItemsAdded, "");
    
     // 註冊保存刪除項的隱藏字段
    Page.RegisterHiddenField( this.HFItemsRemoved, "");
    
     // 註冊包含在UtilMethods裏的一段客戶端javascript
     if (!Page.IsClientScriptBlockRegistered( "UtilMethods"))
        Page.RegisterClientScriptBlock( "UtilMethods", jsScript);
}
 
當你要寫本身自定義控件的時候,幾乎每次都要override基類的OnPreRender,這是經過基類增長功能,保存代碼的一般作法。

任何客戶端代碼,像javascript或者任何註冊在父控件的控件,都應該在控件的這個生命週期(PreRender)中實現。

注意,咱們已經在關鍵字UtilMethods註冊了一段javascript代碼。這段代碼提供了兩個客戶端函數,主要是AddItemToList和RemoveItemFromList。這些函數使用了前面所提到的兩個隱藏字段。它們已經實現了這樣一個功能,就是兩個隱藏字段保持同步,以使兩個隱藏字段不會出現同一項。

這段javascript代碼出示以下:

列表3
// 增長新項到 ListBoxID_ADDED 隱藏字段中
function AddListItem(listName, text, value)
{
    var hiddenField = GetHiddenField(listName.id + '_ADDED');
    
     if (hiddenField != null)
    {
         // 加一個分隔符
        var tmp = hiddenField.value;
         if (tmp != '')
            hiddenField.value += ',';
    
         // 加一項到隱藏字段
        hiddenField.value += text + '|' + value;
    
         // 若是刪除字段中有這項則在刪除字段中刪之
        var removeHiddenField = GetHiddenField(listName.id + '_REMOVED');
         if (removeHiddenField != null)
        {
            var removedItems = removeHiddenField.value.split(',');
            removeHiddenField.value = '';
             for (var i = 0; i < removedItems.length; i++)
            {
                 if (value != removedItems[i])
                {
                     // 加一個分隔符
                    var tmp1 = removeHiddenField.value;
                     if (tmp1 != '')
                        removeHiddenField.value += ',';
    
                    removeHiddenField.value += removedItems[i];
                }
            }
        }
    }
}
    
// 將刪除項的value增長到 ListBoxID_REMOVED 隱藏字段中
function RemoveListItem(listName, value)
{
    var hiddenField = GetHiddenField(listName.id + '_REMOVED');
    
     if (hiddenField != null)
    {
         // 加一個分隔符
        var tmp = hiddenField.value;
         if (tmp != '')
            hiddenField.value += ',';
    
        hiddenField.value += value;
    
         // 若是增長字段中有這項則在增長字段中刪之
        var addHiddenField = GetHiddenField(listName.id + '_ADDED');
         if (addHiddenField != null)
        {
            var addedItems = addHiddenField.value.split(',');
            addHiddenField.value = '';
             for (var i = 0; i < addedItems.length; i++)
            {
                 if (addedItems[i].match(value) == null)
                {
                     // 加一個分隔符
                    var tmp1 = addHiddenField.value;
                     if (tmp1 != '')
                        addHiddenField.value += ',';
    
                    addHiddenField.value += addedItems[i];
                }
            }
        }
    }
}
    
// 在頁中找到某個隱藏字段
function GetHiddenField(fieldName)
{
    var hiddenField;
    hiddenField = document.getElementById(fieldName);
    
     if (hiddenField != null)
         return hiddenField;
    
     return null;
}
 
上面這段代碼已經寫到了OnPreRender方法中,稍後當咱們討論「如何使用新控件」的時候再和你們說明如何調用這些方法。

這有三個方法:
AddListItem
RemoveListItem
GetHiddenField

AddListItemt經過取得用於保存增長項的隱藏字段的ID設置該隱藏字段所包含的ListBox控件的增長項。新增長的項將會按照(Text|Value)的格式增長到用於保存增長項的隱藏字段中。若是其中有的項在用於保存刪除項的隱藏字段中,則在用於保存增長項的隱藏字段中刪之。這保證了刪除項保存在用於保存刪除項的隱藏字段,增長項保存在用於保存增長項的隱藏字段的正確性。

RemoveListItem經過取得用於保存刪除項的隱藏字段的ID設置該隱藏字段所包含的ListBox控件的刪除項。被刪除的項的value將會保存到用於保存刪除項的隱藏字段中,每一項用分隔符「,」標記。每次刪除都會檢查一次用於保存增長項的隱藏字段以保證用於保存刪除項的隱藏字段和用於保存增長項的隱藏字段的值不會有衝突。

最後的GetHiddenField是一個在網頁中找到隱藏字段的經常使用方法。


實現IPostBackDataHandler接口
IPostBackDataHandler接口包含兩個主要方法:
IPostBackDataHandler.RaisePostDataChangedEvent
IPostBackDataHandler.LoadPostData

當你繼承了一個控件後怎麼實現這兩個方法呢?

雖然這個問題不是本文的重點,但咱們仍然會簡要的介紹一下繼承自這個接口的控件如何實現這兩個方法。

當一個回發事件發生時,頁面將從新建立控件。最開始建立的是頁面的主控件,而後是其子控件,再而後是子控件的子控件……

每一個控件都將被檢查是否實現了某些接口。IPostBackDataHandler接口就是其中之一。若是這個接口被實現了,就意味着上面所說起的兩個方法被實現了。

這些方法一般被實現去處理每一次的回發數據。默認的,aps.net中的ListBox控件將實現以上的兩個方法。

LostPostData實現的功能是偵測ListBox控件某一被選中的索引,或者在ListBox控件的SelectionMode被設置成Multiple的時候偵測ListBox控件全部被選中的索引。

咱們將這麼實現LoadPostData方法。首先要保留原來方法的默認行爲,即偵測被選中的索引,而後咱們還要獲得在上面所提到的兩個隱藏字段中被保存的數據。

被保存的數據分別在兩個隱藏字段中,「刪除隱藏字段」保存被刪除的項的value,「增長隱藏字段」保存增長項的text和value。

如下代碼顯示了LoadPostData是如何被實現的。

列表4
bool IPostBackDataHandler.LoadPostData( string postDataKey, NameValueCollection postCollection)
                {
                         // 處理被選中的value
                         string[] postedItems = postCollection.GetValues(postDataKey);
                         bool returnValue = false;

                         // 若是沒有被選中的項
                         if (postedItems == null)
                        {
                                 if ( this.SelectedIndex != -1)
                                {
                                        returnValue = true;
                                }

                                 // 處理客戶端的變化去
                                 goto HandleClientChanges;
                        }

                         // 若是SelectionMode是Single模式
                         if ( this.SelectionMode == ListSelectionMode.Single)
                        {
                                 if (postedItems != null)
                                {
                                         // 處理postedItems的第一項,其是被選中的
                                         int index = this.FindByValueInternal(postedItems[0]);
                                         if ( this.SelectedIndex != index)
                                        {
                                                 // 發生變化了
                                                 this.SelectedIndex = index;
                                                returnValue = true;
                                        }
                                }
                        }

                         // 不然SelectionMode是Multiple模式
                         // 新的被選中的Length
                         int numberOfItemsSelected = postedItems.Length;

                         // 原來的被選中的索引集合
                        ArrayList oldSelectedItems = this.SelectedIndicesInternal;

                         // 新集合,Length爲新的被選中的Length
                        ArrayList currentlySelectedItems = new ArrayList(numberOfItemsSelected);

                         // 把全部新的被選中的value塞進來
                         for ( int i = 0; i < numberOfItemsSelected; i++)
                        {
                                currentlySelectedItems.Add( this.FindByValueInternal(postedItems[i]));
                        }

                         // 原來被選中的Length
                         int numberOfSelectedItems = 0;
                         if (oldSelectedItems != null)
                        {
                                numberOfSelectedItems = oldSelectedItems.Count;
                        }

                         // 原來的和新的被選中的是否相同
                         if (numberOfItemsSelected == numberOfSelectedItems)
                        {
                                 for ( int j = 0; j < numberOfItemsSelected; j++)
                                {
                                         int oldSelect = Convert.ToInt32(currentlySelectedItems[j]);
                                         int currentSelect = Convert.ToInt32(oldSelectedItems[j]);

                                         if (oldSelect != currentSelect)
                                        {
                                                 // 標記該項被選中
                                                 this.Items[j].Selected = true;
                                                returnValue = true;
                                        }
                                }
                        }
                         else
                        {
                                 // 原來的和新的被選中的發生了變化(原來的和新的被選中的Length不一樣就確定是發生變化了)
                                returnValue = true;
                        }

                         // 有變化,從新設置Selected(設爲新的)
                         if (returnValue)
                        {
                                 this.SelectInternal(currentlySelectedItems);
                        }


                 // 這部分處理客戶端的變化(項的增減)
                HandleClientChanges:

                         // 從項集合中刪除項
                         // 處理客戶端刪除項操做
                         string itemsRemoved = postCollection[ this.HFItemsRemoved];
                         string[] itemsRemovedCol = itemsRemoved.Split(',');
                         if (itemsRemovedCol != null)
                        {
                                 if ((itemsRemovedCol.Length > 0) && (itemsRemovedCol[0] != ""))
                                {
                                         for ( int i = 0; i < itemsRemovedCol.Length; i++)
                                        {
                                                ListItem itemToRemove = this.Items.FindByValue(itemsRemovedCol[i]);

                                                 // 從集合中刪除該項
                                                Items.Remove(itemToRemove);
                                        }
                                        returnValue = true;
                                }
                        }

                         // 處理客戶端增長項操做
                         string itemsAdded = postCollection[ this.HFItemsAdded];
                         string[] itemsCol = itemsAdded.Split(',');
                         if (itemsCol != null)
                        {
                                 if ((itemsCol.Length > 0) && (itemsCol[0] != ""))
                                {
                                         // counter 用於肯定返回值是什麼
                                         int counter = -1;
                                         for ( int i = 0; i < itemsCol.Length; i++)
                                        {
                                                 string buf = itemsCol[i];
                                                 string[] itemsTokens = buf.Split('|');

                                                 // 經過value檢查是否已集合中已有該value
                                                ListItem it = this.Items.FindByValue(itemsTokens[1]);
                                                 if (it == null)
                                                {
                                                         string text = itemsTokens[0];
                                                         string id = itemsTokens[1];
                                                        ListItem item = new ListItem(text, id);
                                                        Items.Add(item);

                                                         // 更新 counter
                                                        counter++;
                                                }
                                        }
                                        returnValue = counter > -1 ? true : false;
                                }
                        }

                         return returnValue;
                }
 
這段代碼首先實現了原來asp.net下的ListBox控件的LoadPostData方法

第二部分實現了經過用於保存刪除項的隱藏控件存儲的value從ListBox中刪除項的功能

第三部分實現了經過用於保存增長項的隱藏控件存儲的text|value從ListBox中增長項的功能

咱們沒有更多的說RaisePostDataChangedEvent方法,由於它的實現比較簡單。

列表5
void IPostBackDataHandler.RaisePostDataChangedEvent()
{
    OnSelectedIndexChanged(EventArgs.Empty);
}
 
這個方法何時被調用呢?經過LoadPostData返回來的Boolean值來判斷是否觸發該方法。若是返回值爲true,則證實被選中的值發生了變化,因此須要調用OnSelectedIndexChanged這個方法。

另外,若是LoadPostData返回的值爲false,則證實選中的值沒有發生變化,因此不用調用OnSelectedIndexChanged方法。

經過以上所說的,咱們已經完成了一高級ListBox控件。接下來的部分,咱們將看到一個例子來講明如何在webform中使用這個控件,如何調用前面所說起的那兩個javascript函數。


如何使用新控件
在這一部分中,咱們演示一個包含新的ListBox控件的webform,這個新的ListBox控件就是咱們上面所建立的那個控件。

增長一個按鈕來處理從ListBox中刪除項的操做。

增長兩個textbox控件,讓用戶輸入爲了插入ListBox裏的text和value。另外,再增長一個按鈕使新輸入的項能夠被插到ListBox中。

上面這些按鈕都是客戶端按鈕,所以,ListBox的內部的各個項能夠在客戶端使用javascript來操做。

最後,一個服務端的按鈕用於強制回發到服務端,同時ListBox各個項的變化都將被保存。

這個webfrom以下圖所示。
圖1
當你在ListBox中選擇了一項,按下刪除按鈕時,所選擇的項將從ListBox中刪除,同時被刪除的項的value將被添加到用於保存刪除項的隱藏字段中。實現這個功能的代碼以下。

列表6
function RemoveItem()
        {
                 // 獲得ListBox
                var sourceListBox = document.getElementById('ListBox1');
                
                 // 檢查ListBox是否爲null
                 if (sourceListBox != null)
                {
                         // 得到被選項的value
                        var selectedValue = sourceListBox.options[sourceListBox.options.selectedIndex].getAttribute( "value");
                        
                         // 從ListBox中刪除該項
                        sourceListBox.remove(sourceListBox.options.selectedIndex);
                        
                         // 調用咱們的ListBox輸出到客戶端的函數
                        RemoveListItem(sourceListBox, selectedValue);                        
                }
        }
 
你能夠看到,這些代碼已經有了自身的註釋。當咱們從ListBox中刪除項的時候,咱們已經能夠檢測到被選項了。最後,咱們示例了一下如何調用新的ListBox中的那些客戶端方法。

ListBox的ID和要被刪除的項的value做爲RemoveListItem須要的參數。

當你要向ListBox中增長項的時候,你只要簡單的在兩個textbox輸入text和value,而後按「增長新項」按鈕便可。這個按鈕所觸發的客戶端代碼以下:

列表7
function AddItem()
        {
                 // 獲得ListBox
                var sourceListBox = document.getElementById('ListBox1');
                var txt_text = document.getElementById('TextBox1');
                var txt_value = document.getElementById('TextBox2');
                
                 // 檢查ListBox是否爲null
                 if ((sourceListBox != null) && (txt_text != null) && (txt_value != null))
                {
                         // 建立一個新項
                        var newOption = new Option();    
                        newOption.text = txt_text.value;
                        newOption.value = txt_value.value;
                        
                         // 增長新建立的項到ListBox
                        sourceListBox.options[sourceListBox.length] = newOption;
                        
                         // 調用咱們的ListBox輸出到客戶端的函數
                        AddListItem(sourceListBox,newOption.text, newOption.value);
                }
         }
 
首先建立一個新項,而後把這個新項插入到ListBox中,以後再把新項增長到用於保存增長項的隱藏字段中。

在回發服務器後,你應該注意到ListBox中的各個項與發送服務端以前的各個項是相同的。這就證實這個控件已經達到了咱們預期的功能。


下載
[原文×××]
[譯者改後×××] 


結論在這篇文章裏,咱們一塊兒知道了如何建立一個基於asp.net裏的ListBox控件的新的自定義控件。另外,咱們一塊兒瞭解瞭如何解決ListBox和所用繼承自ListControl基類的控件的客戶端操做沒法在回傳服務端後保存的問題。但願你喜歡這篇文章祝.net開發愉快。
相關文章
相關標籤/搜索