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

为您的 ASP.NET 网格添加数据导出功能

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009年12月23日

CPOL

8分钟阅读

viewsIcon

27704

本文介绍如何使用 ComponentOne 的 PDF 和 Excel 组件导出 Grid 控件的内容,以便用户可以轻松地分析、注释和分发数据。GridView、PDF 和 Excel 组件都包含在 Studio Enterprise 中。

为您的 ASP.NET 网格添加数据导出功能

本文介绍如何使用 ComponentOne 的 PDF 和 Excel 组件导出 Grid 控件的内容,以便用户可以轻松地分析、注释和分发数据。GridViewPDFExcel 组件都包含在 Studio Enterprise 中。

引言

在我们开始之前,让我简要介绍一下我们将要介绍的 ComponentOne 组件。首先,网格控件(ComponentOne GridView for ASP.NET)是一个强大、易于使用的网格控件,具有快速的数据访问和数据呈现功能,如 Microsoft Outlook 风格的分组、过滤和自定义导航。我们的 PDF 组件(ComponentOne PDF for .NET)可以轻松地从您的数据创建丰富的 Adobe PDF 文档,包括安全性、压缩、大纲、超链接等。最后,Excel 组件(ComponentOne Excel for .NET)是一个强大的组件,用于创建或加载 Excel 文件,使任何 .NET 应用程序的数据导出变得容易。

背景

因此,您完成了最新的 ASP.NET 应用程序。它可以高效地加载、格式化和显示重要信息。每个人都喜欢您的应用程序,但偶尔您会收到一些评论,例如“这个应用程序很棒,但如果我能使用 Excel 分析数据就好了”,或者“我希望我能直接在数据上添加注释并将其发送给我的老板”,或者“将数据复制到 HTML 然后进行清理需要花费很多时间”。换句话说,许多用户希望能够将数据导出为 PDF 和 Excel 等有用格式。

为什么?数据很重要,但数据可访问性是任何成功组织的基石。在当今世界,海量数据在我们企业中流动。您的数据及其使用是一项资产。通过使您的数据更易于访问,您将为您的应用程序增加巨大的价值,并使您的组织得以蓬勃发展。

好消息是,使用 ComponentOne 的 C1PdfC1Excel 组件可以轻松实现这一点。我们将向您展示如何实现简单的功能,将任何 C1GridView 控件的内容呈现到 PDF 和 Excel 文件中。最重要的是,将此出色功能添加到您的应用程序只需要几分钟(将代码复制到您的项目,添加几行代码来调用导出方法,然后就完成了)。

Using the Code

首先,下载代码:C1GridViewExport.zip

C1GridView 导出到 PDF 和 Excel 的类名为 C1GridViewExport。使用它非常简单:只需从任何 ASP.NET 页面调用静态的 ExportPdfExportExcel 方法,如下所示。

// export the grid to PDF when the user clicks the PDF button
protected void Pdf_Click(object sender, EventArgs e)
{
    C1GridViewExport.ExportPdf(this.Page, this.C1GridView1);
}
// export the grid to XLS when the user clicks the Excel button
protected void XLS_Click(object sender, EventArgs e) 
{ 
    C1GridViewExport.ExportExcel(this.Page, this.C1GridView1);
}

C1GridViewExport 将网格渲染到适当类型的流中,并将流内容填充到页面响应中。浏览器将向用户显示结果,用户可以将其使用、保存、打印或通过电子邮件发送给他人。

示例应用程序

本文包含的示例应用程序显示了一个加载了 Northwind Products 数据的 C1GridView。在 C1GridView 下方有两个按钮,用于将网格导出为 PDF 或 Excel 文档。这两个按钮都会导致回发,创建包含所需内容的流,并将流复制到页面的 Response 对象中。然后将所需的文档显示给用户,用户可以保存或打印。

在任何时候都不会创建文件,因此您不必担心权限或清理问题。

将组件添加到应用程序

C1PdfC1Excel 组件是收费的。要下载它们以及 C1GridView,请访问http://www.componentone.com/SuperProducts/StudioASPNET/,下载 Studio for ASP.NET 的免费、功能齐全的试用版,其中包含所有三个控件。下载后,打开 Visual Studio 并将它们添加到您的项目中。

要将组件添加到您的项目中,请按照以下步骤操作:

  1. 打开包含要导出网格的 ASP.NET 页面。
  2. 如果组件未出现在 Visual Studio 工具箱中,请通过将 C1.C1Pdf.2.dll 和 C1.C1Excel.2.dll 拖到工具箱中来将它们添加到工具箱,或者右键单击工具箱,选择“选择项...”,然后选择文件。
  3. 仍然打开页面,选择“视图 | 组件设计器”菜单选项。这将显示一个空白窗格。将 C1XLBookC1PdfDocument 组件拖到窗格上。这将向应用程序添加必要的许可信息。(如果您没有许可证,应用程序仍将工作,但输出将包含许可水印)。

导出到 PDF

ExportPdf 方法首先创建一个包含网格内容的 PDF 流。然后,它将流写入页面的 Response 对象。

要创建 PDF 流,代码会通过 C1GridView 类中的 Cells[index].Text 属性检索每个网格单元格的内容。然后,它使用 C1PdfDocument 类中的 MeasureStringDrawString 方法来测量和渲染内容。这些方法类似于 System.Drawing.Graphics 类中的方法。

以下是 ExportPdf 方法的实现。

// export a C1GridView to pdf
public static bool ExportPdf(Page page, C1GridView grid)
{
 // get pdf stream
 var stream = GetPdfStream(grid);
 
 // no stream? we're done
 if (stream == null || stream.Length == 0)
 {
     return false;
 }
 
 // copy stream to Page's Response object
 WriteStreamToPage(page, stream, "application/pdf");
 
 // done
 return true;
}

导出代码的核心是 GetPdfStream 方法。它执行以下任务:

  1. 检查网格是否至少有一个可见列。
  2. 创建一个新的 C1PdfDocument,网格将被渲染到其中。
  3. 计算 PDF 文档中页面的大小(我们使用一英寸的边距)。
  4. 使用 GetColumnWidths 来计算列的宽度,以便它们能适应页面。
  5. 使用 RenderGridRow 方法来渲染网格内容。
  6. 创建一个内存流,将文档保存到其中,然后返回它。

以下是 GetPdfStream 方法的实现。

    // create a Pdf stream with the grid contents
 static MemoryStream GetPdfStream(C1GridView grid)
 {
 int[] mapping;
 List<C1BaseField> columns = GetVisibleColumnsWithMapping(grid, out mapping);
 
 // make sure grid has at least one visible column  
 if (columns.Count == 0)
 {
     return null;
 }
 
 // start with new empty document
 var doc = new C1.C1Pdf.C1PdfDocument();
 
 // get render rectangle (1-inch margin all around)
 var rc = GetPageRectangle(doc);
 
 // calculate column widths
 int cellOffset = grid.RowHeader.Visible ? 1 : 0;
 var widths = GetColumnWidths(doc, grid, rc, cellOffset, columns, mapping);
 
 // render the grid header
 if (grid.ShowHeader)
 {
     var row = grid.HeaderRows[grid.HeaderRows.Length - 1];
     rc = RenderGridRow(doc, rc, grid, row, widths, true, cellOffset, columns, mapping);
 }
 
 // render the grid body
 foreach (C1GridViewRow row in grid.Rows)
 {
    rc = RenderGridRow(doc, rc, grid, row, widths, false, cellOffset, columns, mapping);
 }
 
 // create output stream
 var ms = new MemoryStream();
 doc.Save(ms);
 return ms;
}

在渲染任何内容之前,我们将使用 GetColumnWidths 方法来测量列,并确保它们都能适应页面。最简单的方法是将页面宽度除以列数,使所有列具有相同的宽度。但我们有更好的方法。

下面显示的 GetColumnWidths 方法根据每列的内容来测量其宽度,然后调整总宽度以确保其适合页面。这样,显示长字符串的列将比显示短数值的列宽。

每个单元格的内容使用 C1PdfDocument 类中的 MeasureString 方法进行测量。此方法类似于 System.Drawing.Graphics 类中的 MeasureString 方法。MeasureString 方法以要测量的字符串和用于渲染它的字体作为参数。我们的实现使用了两种字体,一种用于网格标题,一种用于正文。这两种字体都定义为类常量。

以下是计算列宽的代码。

 // calculate column widths to fit the page
 static float[] GetColumnWidths(C1.C1Pdf.C1PdfDocument doc, C1GridView grid, RectangleF rc,
 int cellOffset, List<C1BaseField> columns, int[] mapping)
 {
     // dimension column width vector
     var widths = new float[columns.Count];
 
     // measure header cells
     if (grid.ShowHeader)
     {
         var lastHeaderRow = grid.HeaderRows[grid.HeaderRows.Length - 1];
 
         for (int col = 0; col < columns.Count; col++)
         {
             string text = HttpUtility.HtmlDecode(columns[col].HeaderText);
             var width = doc.MeasureString(text, _headerFont).Width;
             widths[col] = width;
         }
     }
     // measure body cells
     foreach (C1GridViewRow row in grid.Rows)
     {    
          for (int col = 0; col < columns.Count; col++)
          {
              string text = HttpUtility.HtmlDecode(row.Cells[mapping[col] + cellOffset].Text);
              var width = doc.MeasureString(text, _bodyFont).Width;
              widths[col] = Math.Max(widths[col], width);
          }
     }    
     // adjust to fit the page
     float totalWidth = 0;
     for (int col = 0; col < widths.Length; col++)
     {
          totalWidth += widths[col];
     }
     var adjustment = rc.Width / totalWidth;
     if (adjustment < 1)
     {
         for (int col = 0; col < widths.Length; col++)
         {
             widths[col] *= adjustment;
         }
     }
     // done
    
    return widths;
 }

我们快完成了。唯一剩下的方法是渲染网格行的那个。

RenderGridRow 方法以行和布局矩形作为参数。然后,它计算渲染行所需的 heigth。如果页面上有足够的空间,它将渲染该行并返回一个更新的矩形,用于渲染下一行。如果该行不适合当前页面,RenderGridRow 将启动新页面,在页面顶部渲染一个标题行,然后渲染当前行。

要渲染单个单元格,RenderGridRow 首先获取单元格内容作为文本。如果文本可以解析为数字,则单元格右对齐;否则,它左对齐。

如果单元格包含复选框,则代码将选择一个符号字体(WingDings)和表示带或不带复选标记的复选框的相应字符。

以下是 RenderGridRow 实现,这是我们 PDF 渲染器类的最后一部分。

    // render a grid row
    static RectangleF RenderGridRow(C1.C1Pdf.C1PdfDocument doc, RectangleF rc,
        C1GridView grid, C1GridViewRow row, float[] widths, bool header, int cellOffset,
        List<C1BaseField> columns, int[] mapping)
    {
        const int CELL_MARGIN = 4;
        // get row cells
        var cells = row.Cells;
        // calculate cell rectangle
        RectangleF rcCell = rc;
        rcCell.Height = 0;
        // calculate cell height (max of all columns)
        var font = header ? _headerFont : _bodyFont;
        for (int col = 0; col < columns.Count; col++)
        {
             rcCell.Width = widths[col];
             string text = (header)
             ? HttpUtility.HtmlDecode(columns[col].HeaderText)
             : HttpUtility.HtmlDecode(cells[mapping[col] + cellOffset].Text);
             rcCell.Inflate(-CELL_MARGIN, 0);
             float height = doc.MeasureString(text, font, rcCell.Width).Height;
             rcCell.Inflate(CELL_MARGIN, 0);
             rcCell.Height = Math.Max(rcCell.Height, height);
        }
        // break page if we have to
        var rcPage = GetPageRectangle(doc);
        if (!header && rcCell.Bottom > rcPage.Bottom)
        {
            doc.NewPage();
            rc = rcPage;
            if (grid.ShowHeader)
            {
                var lastHeaderRow = grid.HeaderRows[grid.HeaderRows.Length - 1];
                rc = RenderGridRow(doc, rc, grid, lastHeaderRow, widths, true, cellOffset,
                    columns, mapping);
            }
            rcCell.Y = rc.Y;
        }
 
        // center cell content vertically
        var sf = new StringFormat();
        sf.LineAlignment = StringAlignment.Center;
       
        // render data cells
        using (Pen pen = new Pen(Brushes.Gray, 0.1f))
        {
        for (int col = 0; col < columns.Count; col++)
        {
             // get font
             font = header ? _headerFont : _bodyFont;
             // get content
             var cell = cells[mapping[col] + cellOffset];
             string text = (header)
             ? HttpUtility.HtmlDecode(columns[col].HeaderText)
             : HttpUtility.HtmlDecode(cell.Text);
 
             // set horizontal alignment
             double d;
             sf.Alignment = (double.TryParse(text, NumberStyles.Any,
                 CultureInfo.CurrentCulture, out d))
             ? StringAlignment.Far
             : StringAlignment.Near;
 
             // handle check boxes
             if (string.IsNullOrEmpty(text) && cell.Controls.Count > 0 && 
                  cell.Controls[0] is CheckBox)
             {
                 sf.Alignment = StringAlignment.Center;
                 var cb = cell.Controls[0] as CheckBox;
                 text = cb.Checked ? CHKSTR_CHECKED : CHKSTR_UNCHECKED;
                 font = _symbolFont;
             }
             // render cell
             rcCell.Width = widths[col];
             doc.DrawRectangle(pen, rcCell);
             rcCell.Inflate(-CELL_MARGIN, 0);
             doc.DrawString(text, font, Brushes.Black, rcCell, sf);
             rcCell.Inflate(CELL_MARGIN, 0);
             rcCell.Offset(rcCell.Width, 0);
         }  
     }
     // update rectangle and return it
     rc.Offset(0, rcCell.Height);
     return rc;
 }

导出到 Excel

ExportExcel 方法与 ExportPdf 类似,不同之处在于,它不将字符串渲染到文档中,而是使用 C1XLBook 类中的 Sheet[row, col].Value 方法设置单元格值。

以下是 ExportExcel 方法的实现。

// export a C1GridView to an Excel stream
public static bool ExportExcel(Page page, C1GridView grid)
{
    // get excel stream
    var stream = GetExcelStream(grid);
 
    // no stream? we're done
    if (stream == null || stream.Length == 0)
    {
        return false;
    }
 
    // copy stream to Page's Response object
    WriteStreamToPage(page, stream, "application/vnd.ms-excel");
    // done
    return true;
}

导出代码的核心是 GetExcelStream 方法。它执行以下任务:

  1. 检查网格是否至少有一个可见列。
  2. 创建一个新的 C1XLBook,网格将被渲染到其中。
  3. 扫描和解析单元格(数字、日期、布尔值、字符串)。
  4. 将单元格值分配给相应的网格单元格。
  5. 创建一个内存流,将文档保存到其中,然后返回它。

以下是 GetExcelStream 方法的实现。

    // create an Excel stream for a C1GridView
    static MemoryStream GetExcelStream(C1GridView grid)
    {
        int[] mapping;
        List<C1BaseField> columns = GetVisibleColumnsWithMapping(grid, out mapping);
 
        // make sure grid has at least one visible column
        if (columns.Count == 0)
        {
            return null;
        }
        // start with new empty book
        var book = new C1.C1Excel.C1XLBook();
        var sheet = book.Sheets[0];
       
        // export header cells
        if (grid.ShowHeader)
        {
            for (int col = 0; col < columns.Count; col++)
            {
                sheet[0, col].Value = columns[col].HeaderText;
            }
        }
 
        // get row and cell offset
        int rowOffset = grid.ShowHeader ? 1 : 0;
        int cellOffset = grid.RowHeader.Visible ? 1 : 0;
       
        // export body
        double dbl;
        DateTime dateTime;
        for (int row = 0; row < grid.Rows.Count; row++)
        {
            for (int col = 0; col < columns.Count; col++)
            {
                // get cell and value
                var cell = grid.Rows[row].Cells[mapping[col] + cellOffset];
                string text = HttpUtility.HtmlDecode(cell.Text);
                if (string.IsNullOrEmpty(text) && cell.Controls.Count > 0 &&
                    cell.Controls[0] is CheckBox)
                {
                    // boolean value
                    var cb = cell.Controls[0] as CheckBox;
                    sheet[row + rowOffset, col].Value = cb.Checked;
                }
                else if (double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture,
                    out dbl))
                {
                    // numeric value
                    sheet[row + rowOffset, col].Value = dbl;
                }
                else if (DateTime.TryParse(text, CultureInfo.CurrentCulture,
                    DateTimeStyles.None, out dateTime))
                {
                    // date/time value
                    sheet[row + rowOffset, col].Value = dateTime;
                }
                else
                {
                    // everything else is text
                    sheet[row + rowOffset, col].Value = text;
                }
            }   
        }
        // freeze header row and give it a background
        if (grid.ShowHeader)
        {
            sheet.Rows.Frozen = 1;
            var style = new C1.C1Excel.XLStyle(book);
            style.BackColor = Color.LightGray;
            sheet.Rows[0].Style = style;
        }
        
        // create and return stream
        var ms = new MemoryStream();
        book.Save(ms);
        return ms;
    }

GetExcelStream 方法比前面介绍的 GetPdfStream 方法要简单得多。在这种情况下,我们不测量内容、设置列宽、行高或处理分页。

代码中最重要的一部分是解析单元格内容,以便为单元格的 Value 属性分配正确类型的值。这确保了数字、日期和布尔值将作为它们本身保存在 Excel 流中(而不是将所有内容保存为字符串)。

将流写入页面

一旦输出流准备就绪(PDF 或 Excel),就必须将它们写入页面的 Response 流中。这可以通过下面的 WriteStreamToPage 方法完成。

// write a stream into the page response object
static void WriteStreamToPage(Page page, MemoryStream stream, string contentType)
{
    // get response object, clear it
    var rsp = page.Response;
    rsp.Clear();
    rsp.ClearContent();
    rsp.ClearHeaders();
  
    // add Accept-Header header (required when https is used)
    string len = stream.Length.ToString();
    rsp.AddHeader("Accept-Header", len);
 
    // add Content-Length header
    rsp.AddHeader("Content-Length", len);
  
    // write pdf stream into response buffer
    rsp.ContentType = contentType;
    rsp.OutputStream.Write(stream.GetBuffer(), 0, (int)stream.Length);
 
    // done
    rsp.Flush();
    rsp.SuppressContent = true;
}

正如您所见,WriteStreamToPage 方法非常简单。您需要担心的唯一事情是为 contentType 参数传递正确的值。对于 PDF 流,它应该是“application/pdf”。对于 Excel 流,它应该是“application/vnd.ms-excel”。

结论

向 Web 应用程序添加 PDF 和 Excel 输出可以使其功能更加强大。您的应用程序的最终用户将非常乐意能够轻松获取这些数据。只需借助 ComponentOne 的 PDF 和 Excel 库,您就可以扩展应用程序中的任何数据。这里介绍的 C1GridViewExport 类可以让您做到这一点。因此,花点时间让您的数据变得易于访问。

要了解有关 Studio for ASP.NET 控件的更多信息,请访问ComponentOne 网站。

© . All rights reserved.