上篇中我們實作了 GridView 的 TBDropDownField 欄位類別,不過眼尖的讀者不知有沒有發覺我們並處理 Items 屬性取得成員清單的動作,而是直接設定儲存格內含的 TBDropDownList 控制項相關屬性 (DataSourceID、DataTextField、DataValueField 屬性) 後,就由 TDropDownList 控制項自行處理 Items 屬性的資料繫結。當 GridView 的資料列是編輯狀態時,下拉清單會顯示出 Items 的文字內容;可是瀏覽狀態的資料列,卻是顯示欄位原始值,無法呈現 Items 的文字內容。本文將說明如何自行處理 TBDropDownField 的 Items 屬性的資料繫結動作,並使唯讀狀態的資料列也可以呈現 Items 的文字內容。

image

程式碼下載:ASP.NET Server Control - Day24.rar
Northwnd 資料庫下載:
NORTHWND.rar

 

 

一、Items 屬性的問題

我們重新看一次原本 TBDropDownField 類別在處理 Items 屬性的資料繫結取得清單內容的程式碼,在覆寫 InitializeDataCell 方法中,當儲存格為編輯模式時,會呈現 TBDropDownList 控制項並設定取得 Items 清單內容的相關屬性,讓 TBDropDownList 自行去處理它的 Items 屬性的清單內容。

 

                        '由資料來源控制項取得清單項目
                        oDropDownList.DataSourceID = Me.DataSourceID
                        oDropDownList.DataTextField = Me.DataTextField
                        oDropDownList.DataValueField = Me.DataValueField

 

不知你有沒有發覺,我們無論在 InitializeDataCell 及 OnDataBindField 方法中,都沒有針對 TBDropDownList 控制項做任何 DataBind 動作,那它是怎麼從 DataSourceID 關聯的資料來源擷取資料呢?因為 GridView 在執行 DataBind 時,就會要求所有的子控制項做 DataBind,所以我們只要設定好 TBDropDownList 控制項相關屬性後,當 TBDropDownList 自動被要求資料繫結時就會取得 Items 的清單內容。

當然使用 TBDropDownList 控制項去處理 Items 的資料繫結動作最簡單,可是這樣唯讀的儲存格只能顯示原始欄位值,無法呈現 Items 中對應成員的文字;除非無論唯讀或編輯狀態,都要建立 TBDropDownList 控制項去取得 Items 清單內容,而唯讀欄位使用 TBDropDownList.Items 去找到對應成員的顯示文字,不過這樣的作法會怪怪的,而且沒有執行效能率。所以比較好的辨法,就是由 TBDropDownField 類別自行處理 Items 的資料繫結,同時提供給唯讀狀態的 DataControlFieldCell 及編輯狀態的 TBDropDownList 使用。

 

二、由 TBDropDownField 類別處理 Items 屬性的資料繫結

我們要自行處理 Items 屬性來取得成員清單,在 InitializeDataCell 方法中無須處理 Items 屬性,只需產生儲存格需要的子控制項,未來在執行子控制項的 DataBinding 時的 OnDataBindField 方法中再來處理 Items 屬性。

 

        Protected Overrides Sub InitializeDataCell( _
            ByVal Cell As DataControlFieldCell, _
            ByVal RowState As DataControlRowState)

            Dim oDropDownList As TBDropDownList
            Dim oControl As Control

            If Me.CellIsEdit(RowState) Then
                oDropDownList = New TBDropDownList()
                oControl = oDropDownList
                Cell.Controls.Add(oControl)
            Else
                oControl = Cell
            End If

            If (oControl IsNot Nothing) AndAlso MyBase.Visible Then
                AddHandler oControl.DataBinding, New EventHandler(AddressOf Me.OnDataBindField)
            End If
        End Sub

 

OnDataBindField 方法中,我們加上一段處理 Items 屬性的程式碼如下,會利用 PerformSelecrt 私有方法,由關聯的資料來源 (即 DataSrouceID 指定的資料來源控制項) 擷取資料並產生 Items 的成員清單,在後面會詳細講解 PerformSelecrt 方法處理擷取資料的細節。因為 TBDropDownField 每個資料儲存格都會執行 OnDataBindField 方法,但 Items 取得成員清單的動作只需做一次即可,所以會以 FIsPerformSelect 區域變數來判斷是否已取得 Items 的成員清單,若已取過就不重新取得,這樣比較有執行效能。

 

            If Not Me.DesignMode Then
                If Not FIsPerformSelect Then
                    '從關聯的資料來源擷取資料
                    PerformSelect()
                    FIsPerformSelect = True
                End If
            End If

 

當取得儲存儲的對應的欄位值時,依此欄位值由 Items 集合去取得對應的 ListItem 成員,並以此 ListItem.Text 的文字內容來做顯示。

 

            '由 Items 去取得對應成員的顯示內容
            oListItem = Me.Items.FindByValue(CCStr(sText))
            If oListItem IsNot Nothing Then
                sText = oListItem.Text
            End If

 

若是由 TBDropDownList 所引發的 OnDataBindField 方法時,使用 SetItems 私有方法將 TBDropDownField.Items 屬性複製給 TBDropDownList.Item 屬性。

 

                ODropDownList = DirectCast(oControl, TBDropDownList)
                SetItems(ODropDownList)

 

SetItems 私有方法的程式碼如下。

        Private Sub SetItems(ByVal DropDownList As TBDropDownList)
            Dim oItems() As ListItem

            If Not Me.DesignMode Then
                ReDim oItems(Me.Items.Count - 1)
                Me.Items.CopyTo(oItems, 0)
                DropDownList.Items.AddRange(oItems)
            End If
        End Sub

 

三、由關連的資料來源擷取資料

再來就是重點就是要處理 PerformSelecrt 私有方法,來取得 Items 屬性的成員清單內容。PerformSelect 方法的作用是去尋找頁面上的具 IDataSource 介面的控制項,並執行此資料來源的 Select 方法,以取得資料來設定 Items 的清單內容。

step1. 尋找資料來源控制項
PerformSelect 方法中有使用 FindControlEx 方法,它是自訂援尋控制項的多載方法,是取代 FindControl 進階方法。程式碼中使用 FindControlEx 去是頁面中以遞迴方式尋找具有 IDataSource 介面的控制項,且 ID 屬性值為 TBDropDownList.ID 的屬性值。

step2. 執行資料來源控制項的 Select 方法

當找到資料來源控制項後 (如 SqlDataSource、ObjectDataSource ...等等),執行其 DataSourceView.Select 方法,此方法需入一個 DataSourceViewSelectCallback 函式當作參數,當資料來源控制項取得資料後回呼我們指定的 OnDataSourceViewSelectCallback 函式中做後序處理。

step3. 將取得的資料來設定生 Items 的清單內容

在 OnDataSourceViewSelectCallback 函式中接到回傳的具 IEnumerable 介面的資料,有可能是 DataView、DataTable ...等型別的資料。利用 DataBinder.GetPropertyValue 來取得 DataTextField 及 DataValueField 設定的欄位值,逐一建立 ListItem 項目,並加入 Items 集合屬性中。

 

        ''' <summary>
        ''' 從關聯的資料來源擷取資料。
        ''' </summary>
        Private Sub PerformSelect()
            Dim oControl As Control
            Dim oDataSource As IDataSource
            Dim oDataSourceView As DataSourceView

            '若未設定 DataSourceID 屬性則離開
            If StrIsEmpty(Me.DataSourceID) Then Exit Sub
            '找到具 IDataSource 介面的控制項
            oControl = FindControlEx(Me.Control.Page, GetType(IDataSource), "ID", Me.DataSourceID)
            If oControl Is Nothing Then Exit Sub

            oDataSource = DirectCast(oControl, IDataSource)
            oDataSourceView = oDataSource.GetView(String.Empty)
            oDataSourceView.Select(DataSourceSelectArguments.Empty, _
                        New DataSourceViewSelectCallback(AddressOf Me.OnDataSourceViewSelectCallback))
        End Sub

        ''' <summary>
        ''' 擷取資料的回呼函式。
        ''' </summary>
        ''' <param name="data">取得的資料。</param>
        Private Sub OnDataSourceViewSelectCallback(ByVal data As IEnumerable)
            Dim oCollection As ICollection
            Dim oValue As Object
            Dim oItem As ListItem

            Me.Items.Clear()
            If data Is Nothing Then Exit Sub

            oCollection = TryCast(data, ICollection)
            Me.Items.Capacity = oCollection.Count

            For Each oValue In data
                oItem = New ListItem()
                If StrIsNotEmpty(Me.DataTextField) Then
                    oItem.Text = DataBinder.GetPropertyValue(oValue, DataTextField, Nothing)
                End If
                If StrIsNotEmpty(Me.DataValueField) Then
                    oItem.Value = DataBinder.GetPropertyValue(oValue, DataValueField, Nothing)
                End If
                Me.Items.Add(oItem)
            Next
        End Sub

 

 

四、測試程式

使用上篇中同一個案例做測試,同樣以 Northwnd 資料庫的 Products 資料表為例。在 GridView 加入自訂的 TBDropDownField 欄位繫結 CategoryID 欄位,並設定 DataSourceID、DataTextField、DataValueField 屬性;另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 CategoryID 欄位來做比較。

 

                <bee:TBDropDownField  HeaderText="CategoryID"  
                    SortExpression="CategoryID" DataField="CategoryID" 
                    DataTextField="CategoryName" DataValueField="CategoryID" DataSourceID="SqlDataSource2">
                </bee:TBDropDownField>
                <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
                    SortExpression="CategoryID"  ReadOnly="true" />

 

執行程式,在 GridView 瀏覽的模式時,TBDropDownField 的儲存格已經會呈現 Items 對應成員的顯示文字。

image

執行資料列編輯時,也可以正常顯示下拉清單的內容。

image

 

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


DotBlogs Tags: DataControlField DropDownField DropDownList GridView ServerControl

Feedback

  • sam 2009/1/6 下午 12:03 回覆

    # re: [ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結

    老師您好

    之前請教老師的問題學生皆解決了 不過遇到新的問題
    麻煩老師指點學生

    我使用老師之前的兩篇文章
    1.十幾行程式碼搞定 Master-Detail GridView內含子 GridView
    2.使用 BasePage 來解決 GridView 執行 RenderControl 產生的錯誤

    讓我自己可以使用Master-Detail GridView內含子 GridView 不過 在這邊運用老師之前的DropDownList文章
    卻出現
    -----------------------------------------------
    輸入字串格式不正確。
    描述: 在執行目前 Web 要求的過程中發生未處理的例外情形。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

    例外詳細資訊: System.FormatException: 輸入字串格式不正確。

    原始程式錯誤:

    在執行目前 Web 要求期間,產生未處理的例外狀況。如需有關例外狀況來源與位置的資訊,可以使用下列的例外狀況堆疊追蹤取得。

    堆疊追蹤:


    [FormatException: 輸入字串格式不正確。]
    Microsoft.VisualBasic.CompilerServices.Conversions.ParseDouble(String Value, NumberFormatInfo NumberFormat) +211
    Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value) +84

    [InvalidCastException: 從字串 "" 至型別 'Integer' 的轉換是無效的。]
    Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value) +238
    Bee.Web.WebControls.TBDropDownList.LoadPostData(String postDataKey, NameValueCollection postCollection) in D:\iThome\Bee.Web\Bee.Web\WebControls\TBDropDownList\TBDropDownList.vb:67
    System.Web.UI.WebControls.DropDownList.System.Web.UI.IPostBackDataHandler.LoadPostData(String postDataKey, NameValueCollection postCollection) +11
    System.Web.UI.Page.ProcessPostData(NameValueCollection postData, Boolean fBeforeLoad) +353
    System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1194
    -----------------------------------------------

    在使用繼承TBBasePage前 DropDownList儲存值並沒有問題
    單使用Master-Detail GridView也沒問題 合體後就出現這個錯誤訊息



  • jeff377 2009/1/7 上午 10:10 回覆

    # re: [ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結

    to sam :
    TBDropDownList 是在 LoadPostData 方法處理用戶端傳入的參數發生型別轉換錯誤,去該方法下中斷點應該就可以很容易除錯了。

  • sam 2009/1/7 上午 11:42 回覆

    # re: [ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結

    請問老師
    Imports System.Web.UI.Design.WebControls
    他ㄧ直出現我沒有這個NAMESPACE

  • sam 2009/2/3 上午 11:56 回覆

    # re: [ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結

    老師您好
    我在存取TBDropDownList 欄位的時候網頁編譯會出現
    ORA-01461: 只有在將值插入資料類型為 LONG 的資料欄時, 才可以連結一個 LONG 值

    描述: 在執行目前 Web 要求的過程中發生未處理的例外情形。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

    例外詳細資訊: System.Data.OracleClient.OracleException: ORA-01461: 只有在將值插入資料類型為 LONG 的資料欄時, 才可以連結一個 LONG 值


    原始程式錯誤:

    在執行目前 Web 要求期間,產生未處理的例外狀況。如需有關例外狀況來源與位置的資訊,可以使用下列的例外狀況堆疊追蹤取得。

    堆疊追蹤:


    [OracleException (0x80131938): ORA-01461: 只有在將值插入資料類型為 LONG 的資料欄時, 才可以連結一個 LONG 值
    ]
    System.Data.OracleClient.OracleConnection.CheckError(OciErrorHandle errorHandle, Int32 rc) +304889
    System.Data.OracleClient.OracleCommand.Execute(OciStatementHandle statementHandle, CommandBehavior behavior, Boolean needRowid, OciRowidDescriptor& rowidDescriptor, ArrayList& resultParameterOrdinals) +990
    System.Data.OracleClient.OracleCommand.ExecuteNonQueryInternal(Boolean needRowid, OciRowidDescriptor& rowidDescriptor) +431
    System.Data.OracleClient.OracleCommand.ExecuteNonQuery() +115
    System.Web.UI.WebControls.SqlDataSourceView.ExecuteDbCommand(DbCommand command, DataSourceOperation operation) +386
    System.Web.UI.WebControls.SqlDataSourceView.ExecuteInsert(IDictionary values) +227
    System.Web.UI.DataSourceView.Insert(IDictionary values, DataSourceViewOperationCallback callback) +86
    System.Web.UI.WebControls.DetailsView.HandleInsert(String commandArg, Boolean causesValidation) +274
    System.Web.UI.WebControls.DetailsView.HandleEvent(EventArgs e, Boolean causesValidation, String validationGroup) +676
    System.Web.UI.WebControls.DetailsView.OnBubbleEvent(Object source, EventArgs e) +95
    System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +37
    System.Web.UI.WebControls.DetailsViewRow.OnBubbleEvent(Object source, EventArgs e) +113
    System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +37
    System.Web.UI.WebControls.Button.OnCommand(CommandEventArgs e) +118
    System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +166
    System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10
    System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13
    System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +36
    System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1565

  • jeff377 2009/2/9 上午 10:24 回覆

    # re: [ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結

    to sam :
    你的錯誤訊息是由 OracleClient 發出來的,不是由控制項所發出來,你應該由 OracleClient 去除錯。

  • sam 2009/2/9 下午 06:58 回覆

    # re: [ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結

    謝謝老師問題已解決
    想請問老師寫的TBDropDownList一問
    當我今天如果讓使用者在新增模式TBDropDownList裡面找不到自己想要的值我設計了一個textbox與button,我輸入我自己的東西按button後TBDropDownList.items.add(textbox.text)將我自己想要的東西出現在TBDropDownList裡面,選取這個新選項也可存進資料庫,當我下次在進入這個頁面這個選項會消失,ㄧ切都符合我自己想要的功能,在其他頁面讀取這些資料的時候,也讀得到,不過當我編輯模式時,我原本的設計也是跟新增模式ㄧ樣,都用TBDropDownList給使用者選取,可是他沒辦法讀取出來,老師我應該怎麼做才可以編輯模式
    TBDropDownList.items.add(textbox.text),ㄧ樣可以存取,但是離開頁面後這個TBDropDownList的新選項會消失,我該怎麼做呢

標題 *
名稱 *
Email (將不會被顯示)
Url
回應
登入後使用進階評論
Please add 4 and 8 and type the answer here: