GridView 是 ASP.NET 中一個相當常用的控制項,在 GridView 可加入 BoundField、CheckBoxField、CommandField、TemplateField ... 等不同型別的欄位,可是偏偏沒有提供在 GridView 中可呈現 DropDownList 的欄位型別;遇到這類需求時,一般的作法都是使用 TemplateField 來處理。雖然 TemplateField 具有相當好的設計彈性。可是在當 GridView 需要動態產生欄位的需求時,TemplateField 就相當麻煩,要寫一堆程式碼自行去處理資料繫結的動作。相互比較起來,BoundField、CheckBoxField ...等這類事先定義類型的欄位,在 GridView 要動態產生這些欄位就相當方便。如果我們可以把一些常用的 GridView 的欄位,都做成類似 BoundField 一樣,只要設定欄位的屬性就好,這樣使用上就會方便許多,所以在本文將以實作 DropDownList 欄位為例,讓大家了解如何去自訂 GridView 的欄位類別。
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 = " "
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 = " ") 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 欄位值。
編輯某筆資料列進入編輯狀態,就會顯示 TBDropDownList 控制項,清單成員為我們在 Items 設定的內容。
使用 TBDropDownList 來做編輯欄位值,按下更新鈕,這時會執行 TBDropDownField.ExtractValuesFromCell 方法,取得儲存格中的值;最後由資料來源控制項將欄位值寫回資料庫。
接下來測試設定 TBDropDownField.DataSourceID 的情況,把 DataSourcID 指向含 Categories 資料表內容的 SqlDataSoruce 控制項。
<bee:TBDropDownField HeaderText="CategoryID"
SortExpression="CategoryID" DataField="CategoryID"
DataTextField="CategoryName" DataValueField="CategoryID" DataSourceID="SqlDataSource2">
</bee:TBDropDownField>
執行程式查看結果,可以發現 TBDropDownList 控制項的清單內容也可以正常顯示 SqlDataSoruce 控制項取得資料。
備註:本文同步發佈於「第一屆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