ASP.NET Grid 中 Formatting AutoGenerateColumns
演示了如何在列动态生成的 GridView 或 DataGrid 中应用条件格式,并将此类代码封装在 IExtenderProvider 控件中。
引言
有时我们需要处理提供动态列的数据源。例如,一个按年份累计总数的存储过程,其列代表当月累计数据;年初时数据列较少,年终时数据列则会很多。当使用 DataGrid
或 GridView
控件显示此类数据时,将 AutoGenerateColumns
设置为 true
会很方便,让 ASP.NET 在运行时自动生成所需的显示列。但不幸的是,通过网格的 Columns
属性,开发者无法获取这样动态生成的列,因此开发者无法实现基于列的格式化。
尽管存在此限制,开发者仍可以在数据绑定期间捕获合适的网格事件来应用自定义格式。此技术还允许基于单个单元格值应用条件格式。此外,应用此类格式的代码可以封装在实现 IExtenderProvider
接口的自定义控件中,从而允许页面设计者对网格应用复杂的条件单元格格式,而无需进行任何编程。
对于 GridView:捕获 RowDataBound 事件
GridView
控件在数据绑定过程中会公开 RowDataBound
事件。此事件的处理程序定义如下签名:
RowDataBound_Handler(Object grid, GridViewRowEventArgs e)
{
}
GridViewRowEventArgs
对象通过其 Row
属性提供对刚绑定完成的表格行的访问。Row
的类型为 GridViewRow
,它是 TableRow
的派生类。因此,有一个 Cells
属性用于访问单个 TableCell
对象。
在本文下载(请参阅文章顶部的链接)的 formattingGridView.aspx 文件中的这个例子中,假设存储过程(或其他数据源)返回的第一列是某种文本类别。其余列(可能一个或多个)是需要右对齐的值,并逐列交替设置背景颜色。为了演示单个单元格的条件格式,还进行了检查,以便为高值应用特殊高亮显示。代码中还对标题单元格进行了样式设置。
private void GV_RowDataBound(object o, GridViewRowEventArgs e)
{
// apply custom formatting to data cells
if (e.Row.RowType == DataControlRowType.DataRow)
{
// set formatting for the category cell
TableCell cell = e.Row.Cells[0];
cell.Width = new Unit("120px");
cell.Style["border-right"] = "2px solid #666666";
cell.BackColor = System.Drawing.Color.LightGray;
// set formatting for value cells
for(int i=1; i<e.Row.Cells.Count; i++)
{
cell = e.Row.Cells[i];
// right-align each of the column cells after the first
// and set the width
cell.HorizontalAlign = HorizontalAlign.Right;
cell.Width = new Unit("90px");
// alternate background colors
if (i % 2 == 1)
cell.BackColor
= System.Drawing.ColorTranslator.FromHtml("#EFEFEF");
// check value columns for a high enough value
// (value >= 8000) and apply special highlighting
if (GetCellValue(cell) >= 8000)
{
cell.Font.Bold = true;
cell.BorderWidth = new Unit("1px");
cell.BorderColor = System.Drawing.Color.Gray;
cell.BorderStyle = BorderStyle.Dotted;
cell.BackColor = System.Drawing.Color.Honeydew;
}
}
}
// apply custom formatting to the header cells
if (e.Row.RowType == DataControlRowType.Header)
{
foreach (TableCell cell in e.Row.Cells)
{
cell.Style["border-bottom"] = "2px solid #666666";
cell.BackColor=System.Drawing.Color.LightGray;
}
}
}
然后,通过设置 OnRowDataBound
属性,以声明方式将该事件附加到 GridView
:
<asp:GridView id="myList" runat="server"
AutoGenerateColumns="true"
OnRowDataBound="GV_RowDataBound"
. . .
>
</asp:GridView>
渲染后,代码生成的网格效果如下:
对于 DataGrid:捕获 ItemDataBound 事件
DataGrid
的技术类似。DataGrid
公开 ItemDataBound
事件,该事件可以访问 DataGridItemEventArgs
对象。该对象包含一个 Item
属性,其 Controls
集合在数据绑定期间由 TableCell
对象填充。formattingDataGrid.aspx 中的以下示例展示了与上述相同的格式应用,但针对的是 DataGrid
:
private void DG_ItemDataBound(object o, DataGridItemEventArgs e)
{
// apply custom formatting to data cells
if (e.Item.ItemType == ListItemType.Item
|| e.Item.ItemType == ListItemType.AlternatingItem)
{
// set formatting for the category cell
TableCell cell = (e.Item.Controls[0] as TableCell);
cell.Width = new Unit("120px");
cell.Style["border-right"] = "2px solid #666666";
cell.BackColor = System.Drawing.Color.LightGray;
// set formatting for value cells
for(int i=1; i<e.Item.Controls.Count; i++)
{
cell = (e.Item.Controls[i] as TableCell);
// right-align each of the column cells after the first
// and set the width
cell.HorizontalAlign = HorizontalAlign.Right;
cell.Width = new Unit("90px");
// alternate background colors
if (i % 2 == 1)
cell.BackColor
= System.Drawing.ColorTranslator.FromHtml("#EFEFEF");
// check value columns for a high enough value
// (value >= 8000) and apply special highlighting
if (GetCellValue(cell) >= 8000)
{
cell.Font.Bold = true;
cell.BorderWidth = new Unit("1px");
cell.BorderColor = System.Drawing.Color.Gray;
cell.BorderStyle = BorderStyle.Dotted;
cell.BackColor = System.Drawing.Color.Honeydew;
}
}
}
// apply custom formatting to the header cells
if (e.Item.ItemType == ListItemType.Header)
{
foreach (TableCell cell in e.Item.Controls)
{
cell.Style["border-bottom"] = "2px solid #666666";
cell.BackColor=System.Drawing.Color.LightGray;
}
}
}
与 GridView
类似,通过设置 OnItemDataBound
属性,以声明方式将此事件处理程序分配给 DataGrid
:
<asp:DataGrid id="myList" runat="server"
AutoGenerateColumns="true"
OnItemDataBound="DG_ItemDataBound"
. . .
>
</asp:DataGrid>
将通用格式封装到自定义扩展器中
也许您或您的组织设计了一些标准的报表格式,这些格式需要基于列或条件的单元格格式。如果您发现自己在项目中的多个网格上应用相同的报表格式,那么将自定义格式代码封装到扩展器控件中可能会很有用。扩展器 是一种自定义组件,它实现 IExtenderProvider
接口,并为现有控件提供附加(扩展)功能。
有许多优秀的文献介绍了 IExtenderProvider
接口,并详细描述了实现细节,包括以下内容:
- IExtenderProvider 接口文档[^] (MSDN)
- 自定义提供程序控件[^] (作者:Dino Esposito,MSDN)
- 使用 ASP.NET 2.0 的属性扩展器提供程序[^] (作者:Alex Soldatov,CodeProject)
- 修复 Visual Studio 的 ASP.NET 设计器中的 IExtenderProvider[^] (作者:Wouter van Vugt,CodeProject)
- ASP.NET 中的扩展器提供程序组件:一个 IExtenderProvider 实现[^] (作者:Frank Robijn,CodeProject)
对于此实现,需要做的还有不多。我们将从一个名为 CustomReportFormatExtender
的新自定义类开始,该类继承自 Control
并实现 IExtenderProvider
。
[ProvideProperty("UseCustomReportFormat", typeof(GridView))]
[ . . . other attributes . . .]
public class CustomReportFormatExtender : Control, IExtenderProvider
{
. . .
}
修饰此类(ProvidePropertyAttribute
)的 ProvidePropertyAttribute
指定应使用名为 UseCustomReportFormat 的新属性来扩展 GridView
类型的控件。当 GridView
上的此属性设置为 true
时,将应用格式化。该扩展器通过实现 IExtenderProvider
接口要求的 CanExtend
()
方法,指示其仅适用于 GridView
控件。
bool IExtenderProvider.CanExtend(object o) { return (o is GridView); }
扩展器控件负责提供存储空间以及设置和检索扩展属性的方法。为了存储扩展属性值,我们有一个自定义的 ExtendedProperties
类。该类记录 GridView
的 ID
及其扩展的 UseCustomReportFormat
布尔属性的值。
public class ExtenderProperties
{
private bool _useCustomReportFormat;
private string _gridID;
public bool UseCustomReportFormat
{
get { return _useCustomReportFormat; }
set { _useCustomReportFormat = value; }
}
public string GridID
{
get { return _gridID; }
set { _gridID = value; }
}
}
扩展器控件在其 Props
属性中保存 ExtenderProperties
的集合。Props
是公开的,并装饰有设计器属性,纯粹是为了确保 Visual Studio 会持久化扩展属性值。如果没有这些关注,Visual Studio 不会记住扩展属性值。
private ExtenderPropertiesCollection _props
= new ExtenderPropertiesCollection();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[Browsable(false)]
public ExtenderPropertiesCollection Props
{
get
{
return _props;
}
}
然后,扩展器控件为每个扩展属性提供 Get
和 Set
方法,遵循 GetPropertyName 和 SetPropertyName 的命名约定。在这种情况下,只有一个布尔型的 UseCustomReportFormat
属性需要公开。集合类 ExtenderPropertiesCollection
使用这些 Get
和 Set
方法中的实用方法进行定义,以简化扩展属性的管理。
public bool GetUseCustomReportFormat(GridView grid)
{
return Props.GetUseCustomReportFormatForGridID(grid.ID);
}
public void SetUseCustomReportFormat(GridView grid, bool value)
{
Props.SetUseCustomReportFormatForGridID(grid.ID, value);
NotifyDesignerOfChange(); // ensure compatibility with Visual Studio
}
最后,扩展器需要提供实际的格式化功能。它通过挂钩所需 GridView
控件的
RowDataBound
事件来实现。格式化代码被封装在 Handle_RowDataBound
函数中,并在扩展器的 OnInit
事件期间分配。
protected void Handle_RowDataBound(object o, GridViewRowEventArgs e)
{
. . . formatting code . . .
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
foreach (ExtenderProperties p in Props)
{
GridView g = FindControl(p.GridID) as GridView;
if (g != null && p.UseCustomReportFormat)
g.RowDataBound += new
GridViewRowEventHandler(Handle_RowDataBound);
}
}
就是这样。这个自定义扩展器对象可以被编译,添加到工具箱,然后拖到 .aspx 页面上。然后,只需通过将其新的 UseCustomReportFormat
属性设置为 true
来指定要格式化的 GridView
控件。
对于页面设计者来说,这提供了一种简单、标准化的方式,以声明式的方式分配复杂的逐单元格格式。本文下载中的示例 gridWithExtender.aspx 演示了此扩展器控件的用法。
动态列格式化的其他方法
在数据绑定期间注入代码以对动态生成的列应用格式化,除了此方法之外,还有其他替代方案。一种替代方法是在将数据源绑定到显示控件之前检查数据源,并以编程方式创建 DataGrid
或 GridView
列。可以设置这些 DataGridColumn
对象(对于 DataGrid
)或 DataControlField
对象(对于 GridView
)的属性,以影响数据绑定期间的格式化行为。此 CodeProject 文章[^] 中的示例下载展示了此方法的一个示例。虽然可以通过这种方式对列进行格式化,而无需额外遍历单个表单元格,但逐单元格的条件格式仍然必须使用本文所述的方法来应用。
摘要
本文介绍了一种在网格列动态生成时,对 GridView
或 DataGrid
应用格式化的一种方法。当 AutoGenerateColumns
设置为 true
时,可以通过捕获合适的事件来在数据绑定期间操纵单元格的外观——对于 GridView
是 RowDataBound
,对于 DataGrid
是 ItemDataBound
。应用于单个 TableCell
对象的格式可以基于单元格中的值进行条件设置。为了增加易用性,通用的报表格式代码可以封装在自定义扩展器控件中,从而为页面设计者提供完全声明式的解决方案。