GridView 是 ASP.NET 中一个相当常用的控件,在 GridView 可加入 BoundField、CheckBoxField、CommandField、TemplateField ... 等不同型别的字段,可是偏偏没有提供在 GridView 中可呈现 DropDownList 的字段型别;遇到这类需求时,一般的作法都是使用 TemplateField  来处理。虽然 TemplateField 具有相当好的设计弹性。可是在当 GridView 需要动态产生字段的需求时,TemplateField 就相当麻烦,要写一堆程序代码自行去处理数据系结的动作。相互比较起来,BoundField、CheckBoxField ...等这类事先定义类型的字段,在 GridView 要动态产生这些字段就相当方便。如果我们可以把一些常用的 GridView 的字段,都做成类似 BoundField 一样,只要设定字段的属性就好,这样使用上就会方便许多,所以在本文将以实作 DropDownList 字段为例,让大家了解如何去自订 GridView 的字段类别。

程序代码下载:ASP.NET Server Control - Day23.rar
Northwnd 数据库下载:NORTHWND.rar

 

一、选择合适的父类别

一般自订 GridView 的字段类别时,大都是由 DataControlField 或 BoundField 继承下来改写。若是字段不需系结数据(如 CommandFIeld),可以由 DataControlFIeld 继承下来,若是字段需要做数据系结时(如 CheckBoxFIld,可以直接由 BoundField 继承下来改写比较方便。

DataControlField 类别是所有类型字段的基底类别,BoundField 类别也是由 DataControlField 类别继承下来扩展了数据系结部分的功能,所以我们要实作含 DropDownList 的字段,也是由 BoundField 继承下来改写。

 

二、自订字段基底类别

在此我们不直接继承 BoundFIeld,而是先撰写一个继承 BoundField 命名为 TBBaseBoundField 的基底类别,此类别提供一些通用的属性及方法,使我们更方便去撰写自订的字段类别。

    ''' <summary>
    ''' 資料欄位基礎類別。
    ''' </summary>
    Public MustInherit Class TBBaseBoundField
        Inherits BoundField
 
        Private FRowIndex As Integer = 0
 
        ''' <summary>
        ''' 資料列是否為編輯模式。
        ''' </summary>
        ''' <param name="RowState">資料列狀態。</param>
        Public Function RowStateIsEdit(ByVal RowState As DataControlRowState) As Boolean
            Return (RowState And DataControlRowState.Edit) <> DataControlRowState.Normal
        End Function
 
        ''' <summary>
        ''' 資料列是否為新增模式。
        ''' </summary>
        ''' <param name="RowState">資料列狀態。</param>
        Public Function RowStateIsInsert(ByVal RowState As DataControlRowState) As Boolean
            Return (RowState And DataControlRowState.Insert) <> DataControlRowState.Normal
        End Function
 
        ''' <summary>
        ''' 資料列是否為編輯或新增模式。
        ''' </summary>
        ''' <param name="RowState">資料列狀態。</param>
        Public Function RowStateIsEditOrInsert(ByVal RowState As DataControlRowState) As Boolean
            Return RowStateIsEdit(RowState) OrElse RowStateIsInsert(RowState)
        End Function
 
        ''' <summary>
        ''' 判斷儲存格是否可編輯(新增/修改)。
        ''' </summary>
        ''' <param name="RowState">資料列狀態。</param>
        Friend Function CellIsEdit(ByVal RowState As DataControlRowState) As Boolean
            Return (Not Me.ReadOnly) AndAlso RowStateIsEditOrInsert(RowState)
        End Function
 
        ''' <summary>
        ''' 資料列索引。
        ''' </summary>
        Friend ReadOnly Property RowIndex() As Integer
            Get
                Return FRowIndex
            End Get
        End Property
 
        ''' <summary>
        ''' 儲存格初始化。
        ''' </summary>
        ''' <param name="Cell">要初始化的儲存格。</param>
        ''' <param name="CellType">儲存格類型。</param>
        ''' <param name="RowState">資料列狀態。</param>
        ''' <param name="RowIndex">資料列之以零起始的索引。</param>
        Public Overrides Sub InitializeCell(ByVal Cell As DataControlFieldCell, ByVal CellType As DataControlCellType, _
            ByVal RowState As DataControlRowState, ByVal RowIndex As Integer)
 
            FRowIndex = RowIndex
            MyBase.InitializeCell(Cell, CellType, RowState, RowIndex)
        End Sub
 
        ''' <summary>
        ''' 是否需要執行資料繫結。
        ''' </summary>
        ''' <param name="RowState">資料列狀態。</param>
        Friend Function RequiresDataBinding(ByVal RowState As DataControlRowState) As Boolean
            If MyBase.Visible AndAlso StrIsNotEmpty(MyBase.DataField) AndAlso RowStateIsEdit(RowState) Then
                Return True
            Else
                Return False
            End If
        End Function
    End Class

 

 

三、实作 TBDropDownField 字段类别

step1. 继承 TBBaseBoundField 类别

首先新增一个类别,继承 TBBaseBoundField 命名为 TBDropDownFIeld 类别,覆写 CreateField 方法,传回 TBDropDownFIeld 对象。

    Public Class TBDropDownField
        Inherits TBBaseBoundField
 
        Protected Overrides Function CreateField() As System.Web.UI.WebControls.DataControlField
            Return New TBDropDownField()
        End Function
    End Class

 

 

 

 

step2. 加入 TBBaseBoundField 的属性

TBBaseBoundField 类别会内含 DropDownList 控件,所以加入设定 DropDownList 控件的对应属性;我们在 TBBaseBoundField 类别加入了 Items 、DataSourceID、DataTextField、DataValueField 属性。其中 Items 属性的型别与 DropDownList.Items 属性相同,都是 ListItemCollection 集合类别,且 Items 属性会储存于 ViewState 中。

        ''' <summary>
        ''' 清單項目集合。
        ''' </summary>
        < _
        Description("清單項目集合。"), _
        DefaultValue(CStr(Nothing)), _
        PersistenceMode(PersistenceMode.InnerProperty), _
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _
        Editor(GetType(ListItemsCollectionEditor), GetType(UITypeEditor)), _
        MergableProperty(False), _
        Category("Default")> _
        Public Overridable ReadOnly Property Items() As ListItemCollection
            Get
                If (FItems Is Nothing) Then
                    FItems = New ListItemCollection()
                    If MyBase.IsTrackingViewState Then
                        CType(FItems, IStateManager).TrackViewState()
                    End If
                End If
                Return FItems
            End Get
        End Property
 
        ''' <summary>
        ''' 資料來源控制項的 ID 屬性。
        ''' </summary>
        Public Property DataSourceID() As String
            Get
                Return FDataSourceID
            End Get
            Set(ByVal value As String)
                FDataSourceID = value
            End Set
        End Property
 
        ''' <summary>
        ''' 提供清單項目文字內容的資料來源的欄位。
        ''' </summary>
        < _
        Description("提供清單項目文字內容的資料來源的欄位。"), _
        DefaultValue("") _
        > _
        Public Property DataTextField() As String
            Get
                Return FDataTextField
            End Get
            Set(ByVal value As String)
                FDataTextField = value
            End Set
        End Property
 
        ''' <summary>
        ''' 提供清單項目值的資料來源的欄位。
        ''' </summary>
        Public Property DataValueField() As String
            Get
                Return FDataValueField
            End Get
            Set(ByVal value As String)
                FDataValueField = value
            End Set
        End Property

 

 

 

 

step3.建立儲存格內含的控制項

GridView 是以儲存格 (DataControlFieldCell) 為單位,我們要覆寫 InitializeDataCell 方法來建立儲存格中的控制項;當儲存格為可編輯狀態時,就建立 DropDownList 控制項並加入儲存格中,在此使用上篇文章提及的 TBDropDownList 控制項來取代,以解決清單成員不存在造成錯誤的問題。若未設定 DataSourceID 屬性時,則由 Items 屬性取得自訂的清單項目;若有設定 DataSourceID 屬性,則由資料來源控制項 (如 SqlDataSource、ObjectDataSource 控制項) 來取得清單項目。
當建立儲存格中的控制項後,需要以 AddHeadler 的方法,將此控制項的 DataBinding 事件導向 OnDataBindField 這個事件處理方法,我們要在 OnDataBindField 處理資料繫結的動作。

        ''' <summary>
        ''' 資料儲存格初始化。
        ''' </summary>
        ''' <param name="Cell">要初始化的儲存格。</param>
        ''' <param name="RowState">資料列狀態。</param>
        Protected Overrides Sub InitializeDataCell( _
            ByVal Cell As DataControlFieldCell, _
            ByVal RowState As DataControlRowState)
 
            Dim oDropDownList As TBDropDownList
            Dim oItems() As ListItem
            Dim oControl As Control
 
            If Me.CellIsEdit(RowState) Then
                oDropDownList = New TBDropDownList()
                oControl = oDropDownList
                Cell.Controls.Add(oControl)
 
                If Not Me.DesignMode Then
                    If StrIsEmpty(Me.DataSourceID) Then
                        '自訂清單項目
                        ReDim oItems(Me.Items.Count - 1)
                        Me.Items.CopyTo(oItems, 0)
                        oDropDownList.Items.AddRange(oItems)
                    Else
                        '由資料來源控制項取得清單項目
                        oDropDownList.DataSourceID = Me.DataSourceID
                        oDropDownList.DataTextField = Me.DataTextField
                        oDropDownList.DataValueField = Me.DataValueField
                    End If
                End If
            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

 

step4. 处理数据系结

当 GridView 控件在执行数据系结时,储存格的控件就会引发 DataBinding 事件,而这些事件会被导向 OnDataBindField 方法来统一处理储存格中控件的系结动作。

       ''' <summary>
        ''' 將欄位值繫結至 BoundField 物件。 
        ''' </summary>
        ''' <param name="sender">控制項。</param>
        ''' <param name="e">事件引數。</param>
        Protected Overrides Sub OnDataBindField(ByVal sender As Object, ByVal e As EventArgs)
            Dim oControl As Control
            Dim ODropDownList As TBDropDownList
            Dim oNamingContainer As Control
            Dim oDataValue As Object            '欄位值
            Dim bEncode As Boolean              '是否編碼
            Dim sText As String                 '格式化字串
 
            oControl = DirectCast(sender, Control)
            oNamingContainer = oControl.NamingContainer
            oDataValue = Me.GetValue(oNamingContainer)
            bEncode = ((Me.SupportsHtmlEncode AndAlso Me.HtmlEncode) AndAlso TypeOf oControl Is TableCell)
            sText = Me.FormatDataValue(oDataValue, bEncode)
 
            If TypeOf oControl Is TableCell Then
                If (sText.Length = 0) Then
                    sText = "&nbsp;"
                End If
                DirectCast(oControl, TableCell).Text = sText
            Else
                If Not TypeOf oControl Is TBDropDownList Then
                    Throw New HttpException(String.Format("{0}: Wrong Control Type", Me.DataField))
                End If
 
                ODropDownList = DirectCast(oControl, TBDropDownList)
 
                If Me.ApplyFormatInEditMode Then
                    ODropDownList.Text = sText
                ElseIf (Not oDataValue Is Nothing) Then
                    ODropDownList.Text = oDataValue.ToString
                End If
            End If
        End Sub

 

 

 

 

step5. 取得储存格中的值

另外我们还需要覆写 ExtractValuesFromCell 方法,取得储存格中的值。这个方法是当 GridView 的编辑数据要准备写入数据库时,会经由 ExtractValuesFromCell 方法此来取得每个储存格的值,并将这些字段值加入 Dictionary 参数中,这个准备写入的字段值集合,可以在 DataSource 控件的写入数据库的相关方法中取得使用。

        ''' <summary>
        ''' 使用指定 DataControlFieldCell 物件的值填入指定的 System.Collections.IDictionary 物件。 
        ''' </summary>
        ''' <param name="Dictionary">用於儲存指定儲存格的值。</param>
        ''' <param name="Cell">包含要擷取值的儲存格。</param>
        ''' <param name="RowState">資料列的狀態。</param>
        ''' <param name="IncludeReadOnly">true 表示包含唯讀欄位的值,否則為 false。</param>
        Public Overrides Sub ExtractValuesFromCell( _
            ByVal Dictionary As IOrderedDictionary, _
            ByVal Cell As DataControlFieldCell, _
            ByVal RowState As DataControlRowState, _
            ByVal IncludeReadOnly As Boolean)
 
            Dim oControl As Control = Nothing
            Dim sDataField As String = Me.DataField
            Dim oValue As Object = Nothing
            Dim sNullDisplayText As String = Me.NullDisplayText
            Dim oDropDownList As TBDropDownList
 
            If (((RowState And DataControlRowState.Insert) = DataControlRowState.Normal) OrElse Me.InsertVisible) Then
                If (Cell.Controls.Count > 0) Then
                    oControl = Cell.Controls.Item(0)
                    oDropDownList = TryCast(oControl, TBDropDownList)
                    If (Not oDropDownList Is Nothing) Then
                        oValue = oDropDownList.Text
                    End If
                ElseIf IncludeReadOnly Then
                    Dim s As String = Cell.Text
                    If (s = "&nbsp;") Then
                        oValue = String.Empty
                    ElseIf (Me.SupportsHtmlEncode AndAlso Me.HtmlEncode) Then
                        oValue = HttpUtility.HtmlDecode(s)
                    Else
                        oValue = s
                    End If
                End If
 
                If (Not oValue Is Nothing) Then
                    If TypeOf oValue Is String Then
                        If (CStr(oValue).Length = 0) AndAlso Me.ConvertEmptyStringToNull Then
                            oValue = Nothing
                        ElseIf (CStr(oValue) = sNullDisplayText) AndAlso (sNullDisplayText.Length > 0) Then
                            oValue = Nothing
                        End If
                    End If
 
                    If Dictionary.Contains(sDataField) Then
                        Dictionary.Item(sDataField) = oValue
                    Else
                        Dictionary.Add(sDataField, oValue)
                    End If
                End If
            End If
        End Sub

 

 

 

 

四、测试程序

辛苦写好 TBDropDownField 字段类别时,接下来就是验收成果的时候。我们以 Northwnd 数据库的 Products 数据表为例,将 TBDropDownList .DataField 设为 CategoryID 字段来做测试。首先我们测试没有 DataSoruceID 的情况,在 GridView 加入自订的 TBDropDownField 字段系结 CategoryID 字段,另外加入另一个 BoundField 的只读字段,也同样系结 CategoryID 字段来做比较。

                <bee:TBDropDownField  HeaderText="CategoryID"  
                    SortExpression="CategoryID" DataField="CategoryID" >
                    <Items>
                    <asp:ListItem Value="">未對應</asp:ListItem>
                    <asp:ListItem Value="2">Condiments</asp:ListItem>
                    <asp:ListItem Value="3">Confections</asp:ListItem>
                    </Items>
                </bee:TBDropDownField>
                <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
                    SortExpression="CategoryID"  ReadOnly="true" />

 

 

 

 

执行程序,在 GridView 在只读模式,TBDropDownFIeld 可以正确的系结 CategoryID 字段值。

image

编辑某笔资料列进入编辑状态,就会显示 TBDropDownList 控件,清单成员为我们在 Items 设定的内容。

image

使用 TBDropDownList 来做编辑字段值,按下更新钮,这时会执行 TBDropDownField.ExtractValuesFromCell 方法,取得储存格中的值;最后由数据来源控件将字段值写回数据库。

image

接下来测试设定 TBDropDownField.DataSourceID 的情况,把 DataSourcID 指向含 Categories 数据表内容的 SqlDataSoruce 控件。

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

 

 

 

执行程序查看结果,可以发现 TBDropDownList 控件的清单内容也可以正常显示 SqlDataSoruce 控件取得数据。

image

 

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

posted on 2008-10-24 01:01  jeff377  阅读(2184)  评论(5编辑  收藏  举报