[ASP.NET 控制項實作 Day29] 解決 DropDownList 成員 Value 值相同產生的問題

DropDownList 控制頁的成員清單中,若有 ListItem 的 Value 值是相同的情形時,會造成 DropDownList 無法取得正確的 SelectedIndex 屬性值、且無法正確引發 SelectedIndexChanged 事件的問題;今天剛好在網路上看到有人在詢問此問題,所以本文將說明這個問題的源由,並修改 DropDownList 控制項來解決這個問題。

DropDownList 控制頁的成員清單中,若有 ListItem 的 Value 值是相同的情形時,會造成 DropDownList 無法取得正確的 SelectedIndex 屬性值、且無法正確引發 SelectedIndexChanged 事件的問題;今天剛好在網路上看到有人在詢問此問題,所以本文將說明這個問題的源由,並修改 DropDownList 控制項來解決這個問題。

程式碼下載:ASP.NET Server Control - Day29.rar

 

一、DropDownList 的成員 Value 值相同產生的問題

我們先寫個測試程式來描述問題,在頁面上放置一個 DropDownList 控制項,設定 AutoPostBack=True,並加入四個 ListItem,其中 "王五" 及 "陳六" 二個 ListItem 的 Value 值相同。

    <asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
            <asp:ListItem Value="0">張三</asp:ListItem>
            <asp:ListItem Value="1">李四</asp:ListItem>
            <asp:ListItem Value="2">王五</asp:ListItem>
            <asp:ListItem Value="2">陳六</asp:ListItem>
    </asp:DropDownList>

 

在 DropDownList 的 SelectedIndexChanged 事件,輸出 DropDownList 的 SelectedIndex 及 SelectedValue 屬性值。

 

    Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList1.SelectedIndexChanged
        Dim sText As String

        sText = String.Format("DropDownList: Index={0} Value={1}", DropDownList1.SelectedIndex, DropDownList1.SelectedValue)
        Me.Response.Write(sText)
    End Sub

 

 

執行程式,在 DropDownList 選取 "李四" 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。

image 

接下來選取 "陳六" 這個選項時,竟然發生奇怪的現象,DorpDownList 竟然顯示相同 Value 值的 "王五" 這個成員的 SelectedIndex 及 SelectedValue 屬性值。

image image

 

二、問題發生的原因

我們先看一下 DropDownList 輸出到用戶端的 HTML 原始碼。

<select name="DropDownList1" onchange="javascript:setTimeout('__doPostBack(\'DropDownList1\',\'\')', 0)" id="DropDownList1">
	<option selected="selected" value="0">張三</option>
	<option value="1">李四</option>
	<option value="2">王五</option>
	<option value="2">陳六</option>
</select>

DropDownList 是呼叫 __doPostBack 函式,只傳入 eventTarget參數 (對應到 __EVENTTARGET 這個 HiddenField) 為 DropDownList 的 ClientID;當 PostBack 回伺服端時,在 DropDownList 的 LoadPostData 方法中,會取得用戶端選取的 SelectedValue 值,並去尋找對應的成員的 SelectedIndex 值。可是問題來了,因為 "王五" 與 "陳六" 的 Value 是相同的值,當在尋找符合 Value 值的成員時,前面的選項 "王五" 會先符合條件而傳回該 Index 值,所以先造成取得錯誤的 SelectedIndex 。

 

Protected Overridable Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As NameValueCollection) As Boolean
    Dim values As String() = postCollection.GetValues(postDataKey)
    Me.EnsureDataBound
    If (Not values Is Nothing) Then
        MyBase.ValidateEvent(postDataKey, values(0))
        Dim selectedIndex As Integer = Me.Items.FindByValueInternal(values(0), False)
        If (Me.SelectedIndex <> selectedIndex) Then
            MyBase.SetPostDataSelection(selectedIndex)
            Return True
        End If
    End If
    Return False
End Function

 

 

三、修改 DropDownList 控制項來解決問題

要解決這個問題最好的方式就是直接修改 DropDownList 控制項,自行處理前端呼叫 __doPostBack 的動作,將用戶端 DropDownList 選擇 SelectedIndex 一併傳回伺服端。所以我們繼承 DropDownList 命名為 TBDropDownList,覆寫 AddAttributesToRender 來自行輸出 PostBack 的用戶端指令碼,我們會用一個變數記錄 AutoPostBack 屬性,並強制將 AutoPostBack 屬性值設為 False,這是為了不要 MyBase 產生 PostBack  的指令碼;然後再自行輸出 AutoPostBack 用戶端指令碼,其中 __doPostBack 的 eventArgument 參數 (對應到 __EVENTARGUMENT 這個 HiddenField) 傳入 this.selectedIndex。

        Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)
            Dim bAutoPostBack As Boolean
            Dim sScript As String

            '記錄 AutoPostBack 值,並將 AutoPostBack 設為 False,不要讓 MyBase 產生 PostBack 的指令碼
            bAutoPostBack = Me.AutoPostBack
            Me.AutoPostBack = False

            MyBase.AddAttributesToRender(writer)

            If bAutoPostBack Then
                MyBase.Attributes.Remove("onchange")
                sScript = String.Format("__doPostBack('{0}',{1})", Me.ClientID, "this.selectedIndex")
                writer.AddAttribute(HtmlTextWriterAttribute.Onchange, sScript)
                Me.AutoPostBack = True
            End If
        End Sub

在頁面上放置一個 TBDropDownList 控制項,設定與上述案例相同的成員清單。

        <bee:TBDropDownList ID="DropDownList2" runat="server" AutoPostBack="True">
            <asp:ListItem Value="0">張三</asp:ListItem>
            <asp:ListItem Value="1">李四</asp:ListItem>
            <asp:ListItem Value="2">王五</asp:ListItem>
            <asp:ListItem Value="2">陳六</asp:ListItem>
        </bee:TBDropDownList>

執行程式查看 TBDropDownList 控制項的 HTML 原始碼,呼叫 __doPostBack 函式的參數已經被修改,eventArgument 參數會傳入該控制項的 selectedIndex。

<select name="DropDownList2" id="DropDownList2" onchange="__doPostBack('DropDownList2',this.selectedIndex)">
	<option selected="selected" value="0">張三</option>
	<option value="1">李四</option>
	<option value="2">王五</option>
	<option value="2">陳六</option>
</select>

接下來還要覆寫 LoadPostData 方法,取得 __EVENTARGUMENT 這個 HiddenField 的值,並判斷與原 SelectedIndex 屬性值是否不同,不同的話傳回 True,使其產生 SelectedIndexChanged 事件。

 

        Protected Overrides Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As NameValueCollection) As Boolean
            Dim values As String()
            Dim iSelectedIndex As Integer

            Me.EnsureDataBound()
            values = postCollection.GetValues(postDataKey)

            If (Not values Is Nothing) Then
                iSelectedIndex = CInt(Me.Page.Request.Form("__EVENTARGUMENT"))
                If (Me.SelectedIndex <> iSelectedIndex) Then
                    MyBase.SetPostDataSelection(iSelectedIndex)
                    Return True
                End If
            End If
            Return False
        End Function

 

 

四、測試程式

在 TBDropDownList 的 SelectedIndexChanged 事件撰寫如下測試程式碼。

 

    Protected Sub DropDownList2_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList2.SelectedIndexChanged
        Dim sText As String

        sText = String.Format("TBDropDownList: Index={0} Value={1}", DropDownList2.SelectedIndex, DropDownList2.SelectedValue)
        Me.Response.Write(sText)
    End Sub

 

執行程式,在 TBDropDownList 選取 "王五" 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。

image

接下選取 Value 值相同的 "陳六" 這個選項,也會正常引發 SelectedIndexChanged ,並顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。

image

 

備註:本文同步發佈於「第一屆iT邦幫忙鐵人賽」,如果你覺得這篇文章對您有幫助,記得連上去推鑒此文增加人氣 ^^
http://ithelp.ithome.com.tw/question/10013457
http://ithelp.ithome.com.tw/question/10013458

ASP.NET 魔法學院