65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Grid 中 Formatting AutoGenerateColumns

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (25投票s)

2006年10月16日

CPOL

6分钟阅读

viewsIcon

189445

downloadIcon

1900

演示了如何在列动态生成的 GridView 或 DataGrid 中应用条件格式,并将此类代码封装在 IExtenderProvider 控件中。

引言

有时我们需要处理提供动态列的数据源。例如,一个按年份累计总数的存储过程,其列代表当月累计数据;年初时数据列较少,年终时数据列则会很多。当使用 DataGridGridView 控件显示此类数据时,将 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 接口,并详细描述了实现细节,包括以下内容:

对于此实现,需要做的还有不多。我们将从一个名为 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 类。该类记录 GridViewID 及其扩展的 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;                
    }
}

然后,扩展器控件为每个扩展属性提供 GetSet 方法,遵循 GetPropertyName 和 SetPropertyName 的命名约定。在这种情况下,只有一个布尔型的 UseCustomReportFormat 属性需要公开。集合类 ExtenderPropertiesCollection 使用这些 GetSet 方法中的实用方法进行定义,以简化扩展属性的管理。

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 演示了此扩展器控件的用法。

动态列格式化的其他方法

在数据绑定期间注入代码以对动态生成的列应用格式化,除了此方法之外,还有其他替代方案。一种替代方法是在将数据源绑定到显示控件之前检查数据源,并以编程方式创建 DataGridGridView 列。可以设置这些 DataGridColumn 对象(对于 DataGrid)或 DataControlField 对象(对于 GridView)的属性,以影响数据绑定期间的格式化行为。此 CodeProject 文章[^] 中的示例下载展示了此方法的一个示例。虽然可以通过这种方式对列进行格式化,而无需额外遍历单个表单元格,但逐单元格的条件格式仍然必须使用本文所述的方法来应用。

摘要

本文介绍了一种在网格列动态生成时,对 GridViewDataGrid 应用格式化的一种方法。当 AutoGenerateColumns 设置为 true 时,可以通过捕获合适的事件来在数据绑定期间操纵单元格的外观——对于 GridViewRowDataBound,对于 DataGridItemDataBound。应用于单个 TableCell 对象的格式可以基于单元格中的值进行条件设置。为了增加易用性,通用的报表格式代码可以封装在自定义扩展器控件中,从而为页面设计者提供完全声明式的解决方案。

© . All rights reserved.