UML软件工程组织

ASP.NET Datagrid创建自定义列
Microsoft
  简介

  不得不承认,为 Microsoft? ASP.NET 编写 Datagrid 代码包括大量的重复工作。尽管我深受该控件的困扰,但我还是不断寻找简化这类任务的捷径。谁都不愿意做重复的工作,对不对?为了摆脱这种烦人的工作,我们要将多个项目中的重复代码封装到一个可重复使用的程序包中。这才是面向对象的编程工作所要解决的问题,使用 Datagrid 也不例外。对于 Datagrid 控件来说,要实现该目的,需要将常用的代码放到一个内置的列类型中(将在第一个示例中使用),然后创建一个可以在多个 Datagrid 对象中重复使用的自定义列类型。
  本文介绍使用标准 TemplateColumn 在一个 Datagrid 列中使用 DropDownList 控件的过程,然后将该逻辑转换为您的自定义 Datagrid 列类型,我称其为 DropDownColumn。已经为您创建了一些免费的 Datagrid 列,您可以到 MetaBuilders.com 下载所需的类型。

  重复使用的情况

  如果您的小组与许多组织一样,已经将业务逻辑和/或数据访问逻辑分成单独的组件或组件集,而剩下的 ASP.NET 文件、ASPX 及其代码只包含纯粹的表示逻辑。(“纯粹”是一个相对的词语。)但是,即使是表示层的逻辑有时也会重复使用,这样,下次某个用户来到您门前说,“我想让我的应用程序在‘财务’方面看起来与苏茜的一样”时,您可以重复使用“财务”应用程序的部分表示内容快速为其构建一个这样的应用程序。您可能还想将一些逻辑打包,在 Web 上销售或在您的 Web 站点分发。ASP.NET 使这一切比以往更容易实现,因为它使您可以创建自己的服务器控件,或从现有的类型导出列类型,从而获得所需的功能。

  DropDownList 方案

  假设您正在本地 Microsoft SQL Server? 中编辑 Northwind 数据库(或做其他工作),您想使您的用户(我们称之为 Ed,仓库保管员)可以编辑 Orders 表。其中一个字段包含运输信息 (ShipVia),Ed 要能够修改该字段的信息。在显示模式下,运输公司应显示为纯文本。当 Ed 单击 Edit(编辑)按钮时,您不光要为他提供一个 TextBox 以编辑运输方式代码(1、2 或 3),还要为他提供一个包含可以选择不同运输公司的 DropDownList。(因为 Ed 记不住哪个运输公司对应哪个号码,所以,DropDownList 方案可以帮助他解决这个问题。)


图 1:选择运输公司
  内置的 Datagrid 列

  了解问题的大概情况后,现在我们后退一步,看一下 ASP.NET 中构建的 5 种 Datagrid 列类型及其父类型 DataGridColumn。

  BoundColumn。这是文本字段的标准显示。它显示为纯文本,但是当 Datagrid 处于“编辑”模式时,它将转换为 TextBox。还可以选择格式化选项。

  HyperlinkColumn。用于显示文本数据,还代表一个 Web 地址 (URL)。URL 可以与显示文本相同,也可以不同,它们都可以单独设置。它显示为 <a href=...> 标记。

  ButtonColumn。它使用户能够按行与网格进行交互。它可以显示为超链接 LinkButton (<a href=...>) 或 Pushbutton (<input type="button">)。单击该按钮时将触发 PostBack,而在 Datagrid 上触发 ItemCommand 事件。

  EditCommandColumn。它与 ButtonColumn 类似,但是它自动创建用于编辑 Datagrid、取消或提交更改的按钮。触发 ItemCommand 事件,以及所单击按钮的特定事件:EditCommand、CancelCommand 或 UpdateCommand。

  TemplateColumn。用于完全控制显示给用户的控件,分为多种模板,例如 ItemTemplate 和 EditItemTemplate。任何 ASP.NET 或 HTML 控件或控件组都可以放置在这些模板中。

  注意:直接使用这些列类型之前,请关闭 AutoGenerateColumns(运行时自动生成列)。然后,您可以在属性生成器中使用这些列类型,或者直接在 ASPX 文件的 HTML 代码中使用。


图 2:从 DataGridColumn 中继承的 5 种内置列

  尽管这些列类型非常有用,它们不过是了解 Datagrid 列内容的开始。

  传统方法:TemplateColumn 中的 DropDownList

  在研究如何创建新列类型之前,首先让我们看一下如何通过直接在 TemplateColumn 内使用 DropDownList 解决下拉列表的问题,而不用自定义列。ItemTemplate 将只包含表示当前值的纯文本表示,而 EditItemTemplate 包含一个需要在运行时管理的 <asp:DropDownList> 控件。

<asp:DataGrid id="DataGrid1" runat="server" CssClass="grid" AutoGenerateColumns="False">
<Columns>
 <asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update" />
 <asp:BoundColumn DataField="OrderID" ReadOnly="True" HeaderText="Order ID" />
 <asp:BoundColumn DataField="ShipName" HeaderText="Ship to" ReadOnly="True" />
 <asp:BoundColumn DataField="ShipCountry" HeaderText="Country" ReadOnly="True" />
 <asp:TemplateColumn HeaderText="Ship Method">
 <ItemTemplate>
  <%#Container.DataItem("ShipVia")%>
 </ItemTemplate>
 <EditItemTemplate>
  <asp:DropDownList runat="server" ID="Dropdownlist1"/>
 </EditItemTemplate>
 </asp:TemplateColumn>
</Columns>
</asp:DataGrid>

  绑定 Datagrid 的代码:

Sub BindGrid()
 Dim SQL As String = "SELECT OrderID,
 ShipName, ShipCountry, ShipVia FROM Orders"
 Dim DA As SqlDataAdapter = New SqlDataAdapter(SQL, ConnStr)
 Dim DS As New DataSet
 DA.Fill(DS, "Orders")
 DataGrid1.DataSource = DS.Tables("Orders").DefaultView
 DataGrid1.DataBind()
End Sub

  当前编辑的项目是在触发 Datagrid 的 ItemDataBound 事件时绑定到 DropDownList 的。使用 ItemDataBound 事件时,请检查当前项目的 ListItemType,否则您可能会发现您正在使用 HeaderItem 或其他不适用的项目类型。为 EditItem 引用 DropDownList 控件。在下面的代码中,我直接使用单元格控件集进行说明(为了与后面的示例保持一致),但是,您可以采用简单的方法,直接为 DropDownList 控件指定 ID,并使用 Datagrid 项目的 FindControl 方法定位控件引用。由于 Datagrid 被绑定到 DataTable 的默认视图,而该视图的元素属于 DataRowView 类型,所以您可以将当前项目的 DataItem 属性转换为一个 DataRowView 实例。这样,您可以按字段名直接引用 DataItem 中的字段。使用这种方法,将“ShipVia”的当前值保存到该记录中,并使用它选择相应的下拉列表项。

Private Sub DataGrid1_ItemDataBound(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) _
Handles DataGrid1.ItemDataBound
 If e.Item.ItemType = ListItemType.EditItem Then
  Dim DRV As DataRowView = CType(e.Item.DataItem, DataRowView)
  Dim CurrentShip As String = DRV("ShipVia")
  Dim DDL As DropDownList = CType(e.Item.Cells(4).Controls(1), DropDownList)
  Dim SQL As String = "SELECT ShipperID, CompanyName FROM Shippers ORDER BY ShipperID"
  Dim DA As SqlDataAdapter = New SqlDataAdapter(SQL, ConnStr)
  Dim DS As New DataSet
  Dim item As ListItem
  DA.Fill(DS, "Shippers")
  DDL.DataSource = DS.Tables("Shippers").DefaultView
  DDL.DataTextField = "CompanyName"
  DDL.DataValueField = "ShipperID"
  DDL.DataBind()
  item = DDL.Items.FindByValue(CurrentShip)
 If Not item Is Nothing Then item.Selected = True
 End If
End Sub

  最后,编写从 DropDownList 中检索当前选定值的代码,并执行数据库更新:

Private Sub DataGrid1_UpdateCommand(ByVal source As Object, _
ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
Handles DataGrid1.UpdateCommand
 Dim DDL As DropDownList = CType(e.Item.Cells(4).Controls(1), DropDownList)
 Dim NewShip As Integer = DDL.SelectedValue
 Dim OrderID As Integer = Int32.Parse(e.Item.Cells(1).Text)
 Dim SQL As String = "UPDATE Orders SET ShipVia=@Ship WHERE OrderID=@ID"
 Dim Conn As SqlConnection = New SqlConnection(ConnStr)
 Dim Cmd As New SqlCommand(SQL, Conn)
 Cmd.Parameters.Add(New SqlParameter("@Ship", NewShip))
 Cmd.Parameters.Add(New SqlParameter("@ID", OrderID))
 Conn.Open()
 Cmd.ExecuteNonQuery()
 Conn.Close()
 DataGrid1.EditItemIndex = -1
 BindGrid()
End Sub
  所有列的基础:DataGridColumn

  我们回顾一下所有内置列类型的父类型 DataGridColumn。(参见图 1。)它包含所有 Datagrid 列中常用的属性和方法。带星号的类型表示您创建自定义列类型时要使用的类型。

DataGridColumn 属性
FooterStyle (TableItemStyle)
FooterText(字符串)
HeaderImageUrl(字符串)
HeaderStyle (TableItemStyle)
HeaderText(字符串)
ItemStyle (TableItemStyle)
SortExpression(字符串)
Visible(布尔值)
DataGridColumn 方法
Initialize
InitializeCell
LoadViewState
OnColumnChanged
SaveViewState
TrackViewState

  可重复使用的方法:创建 DropDownColumn

  首先要在 Microsoft Visual Studio .NET 中创建一个新类库,并将其命名为 MyCustomColumn。添加一个新类 DropDownColumn,并确保在您的类定义中添加命名空间,这样您的初始代码应如下所示:

Namespace MyCustomColumn
Public Class DropDownColumn
Inherits DataGridColumn

Public DataSource As ICollection
Public DataField As String
Public DataTextField As String
Public DataValueField As String

End Class
End Namespace

  我还声明了 4 个公共属性,如下所示:
 
  DataSource。它是用来填充 DropDownList 的数据集。可以是实现 ICollection 接口的任何内容。在本文的示例中,我使用 ArrayList 和 DataView。

  DataField。它是父 Datagrid 数据源中的字段,它与从下拉列表中选定的数据相对应。例如,如果DataSource 包含一个状态集,DataField 将类似于“StateCode”,也可以在表格中使用状态随意命名字段。

  DataTextField。这是要显示在下拉列表中的文本,可以是下面的值,也可以不是。

  DataValueField。这是表示特殊下拉选项的值。DataValueField 通常是一个整数值或其他代码,而 DataTextField 是对用户来说更有意义的文本说明。

  接下来,覆盖 InitializeCell,它是 Datagrid 列的一个固有事件。列中的所有单元格都将发生 InitializeCell,它与直接使用 Datagrid 时的 ItemCreated 事件非常相似。您可以使用它来管理单元格内容,例如设置 HeaderText,添加您将向其中添加数据的 DropDownList 控件。我已经为单元格的 DataBinding 事件添加了处理程序,需要根据当前是否正在编辑行来采取不同的处理方式。每个 System.Web.UI.Control 都有一个 DataBinding 事件,当数据被绑定到控件时,您可以从这里访问底层的数据,本例中为 Datagrid 中的 TableCell 对象。

Public Overrides Sub InitializeCell(ByVal cell As TableCell, _
ByVal columnIndex As Integer, _
ByVal itemType As ListItemType)
 MyBase.InitializeCell(cell, columnIndex, itemType)
 Select Case itemType
  Case ListItemType.Header
   cell.Text = HeaderText
  Case ListItemType.Item, ListItemType.AlternatingItem
   AddHandler cell.DataBinding, AddressOf ItemDataBinding
  Case ListItemType.EditItem
   AddHandler cell.DataBinding, AddressOf EditItemDataBinding
   Dim DDL As New DropDownList
   cell.Controls.Add(DDL)
 End Select
End Sub

  接下来是 ItemDataBinding 例程,当对 Datagrid 中的 Item 或 AlternatingItem 进行数据时,将触发该例程。您需要引用回正在绑定的 TableCell,可以直接转换传递给事件的“发送者”对象,再使用 TableCell 的 NamingContainer 属性引用当前的 DataGridItem。这里只能以纯文本格式显示 DataField 的内容,就象显示在 BoundColumn 中一样。最后,如果用户指定的字段不存在,我将为用户显示更友好的错误信息,而不是仅仅显示“索引超出范围”这样的一般信息。

Private Sub ItemDataBinding(ByVal sender As Object, ByVal e As EventArgs)
Dim cell As TableCell = CType(sender, TableCell)
Dim DGI As DataGridItem = CType(cell.NamingContainer, DataGridItem)
Try
cell.Text = DGI.DataItem(DataField)
Catch RangeEx As IndexOutOfRangeException
Throw New Exception("Specified DataField was not found.")
Catch OtherEx As Exception
Throw New Exception(OtherEx.InnerException.ToString)
End Try
End Sub

  下一步,编写 EditItemDataBinding 事件的代码,当某个行进入“编辑”模式时,将在我们的自定义列单元格上触发该事件。再次引用当前单元格并在调用 InitializeCell 方法时插入 DropDownList 控件。在 DropDownList 中添加一个空项目作为第一个选项,如果列中的当前数据与您从 DataSource 集合中选择并放在列表中的任何项目都不匹配,则选择该选项。

  然后,需要确定所传入集合的类型。在本例中,我将处理两种情况:通过 ArrayList 传递一组字符串,或者数据表中的 DataView,它由 DataRowView 项目构成。对于字符串数据,我将输入一个新的 ListItem,并设置下拉项的值和文本。由于这两种情况是相同的,所以这里只需要文本。但是我将选择相应的项目来根据值作出选择,以便与下一个示例保持一致,下一个示例将设置一个单独的值属性。对于 DataRowView 项目,上一个示例中已指出,DataRowViewInstance("FieldName") 返回一个表示该字段中的数据的对象。可以使用同样的方法检索 DataTextField 和 DataValueFields 需要的值。

  最后,抛出一些异常以处理开发人员使用列时遇到的常见错误,例如向 DataField 属性发送无效字段名,或传入不兼容的 DataSource 类型。我已经对要弹出的异常消息进行了硬编码,但希望您在实际的应用程序中将这些消息保存到更容易配置的位置。例如,如果您希望在全球范围内使用您的应用程序,则可以保存到您的 web.config 文件或资源文件中。同样,您不一定非要再次抛出“未找到指定的 DataField”异常,因为在 Datagrid 被置于“编辑”模式之前可能已经在 ItemDataBinding 事件中捕获了该异常。

Private Sub EditItemDataBinding(ByVal sender As Object, _
ByVal e As EventArgs)
Dim cell As TableCell = CType(sender, TableCell)
Dim DDL As DropDownList = _
CType(cell.Controls(0), DropDownList)
Dim DataSourceItem As Object
Dim item As ListItem
Dim DGI As DataGridItem

'首先添加一个空选项
DDL.Items.Add(New ListItem(""))
For Each DataSourceItem In DataSource
 Select Case DataSourceItem.GetType.ToString
  Case "System.String" '应用到 ArrayList 示例
   item = New ListItem(DataSourceItem, DataSourceItem)
   DDL.Items.Add(item)
  Case "System.Data.DataRowView"
   Dim DRV As DataRowView = _
   CType(DataSourceItem, DataRowView)
   item = New_
   ListItem(DRV(DataTextField), DRV(DataValueField))
   DDL.Items.Add(item)
  Case Else
   Throw New Exception("Invalid DataSource type.")
 End Select
Next

Try
 DGI = CType(cell.NamingContainer, DataGridItem)
 item = DDL.Items.FindByValue(DGI.DataItem(DataField))
Catch RangeEx As IndexOutOfRangeException
 Throw New Exception("Specified DataField was not found.")
Catch OtherEx As Exception
 Throw New Exception(OtherEx.InnerException.ToString)
End Try

If Not item Is Nothing Then item.Selected = True
End Sub
  使用 DropDownColumn

  以上是创建 DropDownColumn 类所需的所有代码,下面我们看一看如何在应用程序中使用该控件。如果您是在家中学习,而且还没有开始做,请将上面创建的命名空间编译到 MyCustomColumn.dll 中,并将其复制到您想试验的应用程序的 /bin 文件夹中。本例中,我创建一个新的 Web 应用程序 UseCustomColumn,并在我的 /bin 目录的 MyCustomColumn.dll 中添加一个引用。在 ASPX 文件的顶部,添加 @Register 指令:

<%@ Register TagPrefix="dgg"
Namespace="MyCustomColumn"
Assembly="MyCustomColumn" %>

  请注意,新的 Datagrid 列类型不会为 Datagrid 出现在 Visual Studio .NET 属性生成器中,因此您需要进入 HTML 视图并在其中添加列声明。确保 Datagrid 声明位于一组 <form runat="server">...</form> 标记之中,这些标记用于处理 PostBack。ASPX 文件的其余部分应如下所示:

<%@ Page Language="vb"
AutoEventWireup="false"
Codebehind="WebForm1.aspx.vb"
Inherits="UseCustomColumn.WebForm1"
Trace="False" Debug="True"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<title>WebForm1</title>
<LINK rel="stylesheet" type="text/css" href="Styles.css">
<meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<asp:DataGrid id="DataGrid1"
runat="server" CssClass="grid"
AutoGenerateColumns="False">
<Columns>
<asp:EditCommandColumn
EditText="Edit" CancelText="Cancel"
UpdateText="Update" />
<asp:BoundColumn
DataField="OrderID"
ReadOnly="True"
HeaderText="Order ID"/>
<asp:BoundColumn
DataField="ShipName"
HeaderText="Ship to"
ReadOnly="True"/>
<asp:BoundColumn
DataField="ShipCountry"
HeaderText="Country"
ReadOnly="True"/>
<dgg:DropDownColumn DataField="ShipVia" HeaderText="Ship Method" />
</Columns>
</asp:DataGrid>
</form>
</body>
</HTML>

  Datagrid 被绑定到 Northwind 示例的 Orders 表,自定义 DropDownColumn 被绑定到 ShipVia 列。现在我只设置 DataField 属性,因为刚刚绑定到一个简单的 ArrayList,不需要 DataTextField 和 DataValueField 属性。如果您有预定义的常数列表或者您需要一个快速设置选项的方法,ArrayList 选项最简单。DropDownColumn 的 DataSource 在代码中设置,首先引用 DropDownColumn:

Dim DDC As MyCustomColumn.DropDownColumn
DDC = CType(DataGrid1.Columns(4), MyCustomColumn.DropDownColumn)
Dim AL As New ArrayList
AL.Add("Shipping Company A")
AL.Add("Shipping Company B")
AL.Add("Shipping Company C")
DDC.DataSource = AL

  下面是运行此代码的结果:


图 3:使用 ArrayList

  接下来,我需要转换该示例以便使用数据库中的活动表。ShipVia 是查找表 Shippers 的外键,我在代码中将其指定为 DropDownColumn 的 DataSource。我还需要改变 DropDownColumn 声明,以包括与 Shippers 表中的相应字段匹配的 DataTextField 和 DataValueField 名称:

<dgg:DropDownColumn DataField="ShipVia" DataTextField="CompanyName"
DataValueField="ShipperID" HeaderText="Ship Method" />

  然后将两个 Orders 表绑定到 Datagrid,将 Shippers 表绑定到自定义列:

Dim SQL As String =
"SELECT OrderID, ShipName, ShipCountry, ShipVia FROM Orders"
Dim DA As SqlDataAdapter = New SqlDataAdapter(SQL, ConnStr)
Dim DS As New DataSet
DA.Fill(DS, "Orders")
'Dim Cmd As SqlCommand = New SqlCommand(SQL, Conn)
'Conn.Open()
'DataGrid1.DataSource = _
Cmd.ExecuteReader(CommandBehavior.CloseConnection)
DataGrid1.DataSource = DS.Tables("Orders").DefaultView
SQL = "SELECT ShipperID, CompanyName " & _
"FROM Shippers ORDER BY ShipperID"
DA.SelectCommand.CommandText = SQL
DA.Fill(DS, "Shippers")
DDC.DataSource = DS.Tables("Shippers").DefaultView
DataGrid1.DataBind()

  DataGridColumn 使用活动数据,根据 Orders 表中的值(1、2 或 3)自动选择正确的项目,如下所示:


图 4:从数据库中检索数据

  使用 DropDownColumn 的最后一步是检索选定的值以传递回数据库更新。为此,只需在单元格内引用 DropDownList 控件,并确定其 SelectedValue 属性:

Private Sub DataGrid1_UpdateCommand( ByVal source As Object, _
ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
Handles DataGrid1.UpdateCommand
 Dim CustomDDL As DropDownList = _
  CType(e.Item.Cells(4).Controls(0), DropDownList)
 Dim NewShip As Integer = CustomDDL.SelectedValue
 Dim OrderID As Integer = Int32.Parse(e.Item.Cells(1).Text)
 Dim SQL As String = "UPDATE Orders SET ShipVia=@Ship WHERE OrderID=@ID"
 Dim Conn As SqlConnection = New SqlConnection(ConnStr)
 Dim Cmd As New SqlCommand(SQL, Conn)
 Cmd.Parameters.Add(New SqlParameter("@Ship", NewShip))
 Cmd.Parameters.Add(New SqlParameter("@ID", OrderID))
 Conn.Open()
 Cmd.ExecuteNonQuery()
 Conn.Close()
 DataGrid1.EditItemIndex = -1
 BindGrid()
End Sub

  小结

  上文概述了如何以 DataGridColumn 为父类型创建一个新类型、如何进行数据绑定以及如何将其应用到实际应用程序中。这只是可重复使用的 Datagrid 列的一个示例,因此,您需要检查您自己的应用程序,以确定哪些重复的功能可以封装到其自己的自定义 Datagrid 列中。您可以开发自己的列,以解决常见问题(例如,在列中显示 DropDownList),或满足您公司的特殊需要。您也不必拘泥于本文的示例,只在自定义列中包含一个 ASP.NET 控件,您可以编写更复杂的结构,例如将一系列控件、第三方内容或整个 Datagrid 控件嵌套到列中,以表现多层信息。总之,您可以充分发挥您的想象力。

  5个内置的列类型非常有用,它们可以满足使用 Datagrid 控件进行显示的大多数情况下的需要。现在并没有开发您自己的控件,只是将一些有意义的内容随便放到 TemplateColumn 中。创建自定义列使您可以突破这些限制,在您的 Datagrid 应用程序中添加丰富的功能。

版权所有:UML软件工程组织