如何在按钮点击时立即将dataGridView数据导出到Excel?

45

我的数据网格视图中有10k行和15列。我希望在按钮点击时将这些数据导出到Excel表中。我已经尝试过以下代码。

private void btExport_Click(object sender, EventArgs e)
    {
        Microsoft.Office.Interop.Excel._Application app  = new Microsoft.Office.Interop.Excel.Application();
        Microsoft.Office.Interop.Excel._Workbook workbook =  app.Workbooks.Add(Type.Missing);        
        Microsoft.Office.Interop.Excel._Worksheet worksheet = null;                   
        app.Visible = true;
        worksheet = workbook.Sheets["Sheet1"];
        worksheet = workbook.ActiveSheet;                  
        for(int i=1;i<dataGridView1.Columns.Count+1;i++)
        {
             worksheet.Cells[1, i] = dataGridView1.Columns[i-1].HeaderText;
        }    
        for (int i=0; i < dataGridView1.Rows.Count-1 ; i++)
        {
            for(int j=0;j<dataGridView1.Columns.Count;j++)
            {
                if (dataGridView1.Rows[i].Cells[j].Value != null)
                {
                    worksheet.Cells[i + 2, j + 1] = dataGridView1.Rows[i].Cells[j].Value.ToString();
                }
                else
                {
                    worksheet.Cells[i + 2, j + 1] = "";
                }
            }
        }
    }

这对我来说可行,但是导出过程需要很长时间才能完成。

是否有可能在单击按钮时即时从包含10k行的dataGridView中导出到Excel?

除此之外,当我尝试将所有dataGridView内容复制到剪贴板,然后手动将其粘贴到Excel工作表中时,几乎可以立即完成。

那么,是否有一种方法可以在单击按钮时将所有dataGridView单元格复制到剪贴板并将其粘贴到具有单元格格式的Excel工作表中?

我已经有了复制到剪贴板的代码,但我不知道如何通过打开它来将其粘贴到新的Excel工作表中。

        private void copyAllToolStripMenuItem_Click(object sender, EventArgs e)
    {
        dataGridView1.SelectAll();
        DataObject dataObj = dataGridView1.GetClipboardContent();
        if (dataObj != null)
            Clipboard.SetDataObject(dataObj);
    }

请提供一个示例。我是C#的新手。

1
几乎所有的答案都写了 (HtmlTextWriter) 字符串 或包含了 Interop 代码。请不要使用这些,否则在处理 DateTime 和 Decimal 格式时会有问题。而且 Excel 会发出警告,因为您生成的不是“真正”的 Excel 文件,而是一个扩展名为 .xls 的 HTML 页面。开始使用专门用于创建 Excel 文件的库,例如 EPPlus。这里有示例这里 - VDWWD
14个回答

74

我通过简单的复制粘贴方法解决了这个问题。我不知道这是最好的方法,但对我来说效果很好,而且几乎是即时的。下面是我的代码。

    private void copyAlltoClipboard()
    {
        dataGridView1.SelectAll();
        DataObject dataObj = dataGridView1.GetClipboardContent();
        if (dataObj != null)
            Clipboard.SetDataObject(dataObj);
    }
    private void button3_Click_1(object sender, EventArgs e)
    {
        copyAlltoClipboard();
        Microsoft.Office.Interop.Excel.Application xlexcel;
        Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
        Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
        object misValue = System.Reflection.Missing.Value;
        xlexcel = new Excel.Application();
        xlexcel.Visible = true;
        xlWorkBook = xlexcel.Workbooks.Add(misValue);
        xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
        Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
        CR.Select();
        xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);          
    }

谢谢。


24
我可以通过将剪贴板复制模式设置为选择标题,来获取标题文本:DataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText DataGridView1.MultiSelect = True DataGridView1.SelectAll() - mack
2
@Jake,是的,它运行得非常好,并添加了Mack步骤以复制标题行。 - Gurunadh
1
我该如何将它们导出到已存在的Excel文件的特定行?特定行可以通过这个来确定:Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[15, 1]; 我该如何向已存在的Excel文件追加数据? - 1teamsah
1
@MeysamTolouee 我也曾经需要知道同样的事情,我找到了如何保存我的结果的方法,我在下面发布了我的代码。 - Cornelius
1
所以我正在使用这段代码,在一些生产机器上它确实粘贴了一张图片。看起来像是“PasteSpecial()”函数粘贴的数据的位图表示。不知道该如何解决这个问题。 - Рахул Маквана
显示剩余6条评论

37

这是一个很好的问题,我惊讶地发现很难找到一个明确而完整的答案,大多数答案都是伪代码或者不完全。

基于Jake的回答,我能够创建一个完整的解决方案,将我的DataGridView中的数据复制并保存到Excel文件中。我发布我的完整解决方案,希望能帮助其他像我一样的C#新手 :)

首先,您需要在项目中引用Microsoft.Office.Interop.Excel引用。请参阅MSDN了解如何添加它。

我的代码:

using Excel = Microsoft.Office.Interop.Excel;

private void btnExportToExcel_Click(object sender, EventArgs e)
{
    SaveFileDialog sfd = new SaveFileDialog();
    sfd.Filter = "Excel Documents (*.xls)|*.xls";
    sfd.FileName = "Inventory_Adjustment_Export.xls";
    if (sfd.ShowDialog() == DialogResult.OK)
    {
        // Copy DataGridView results to clipboard
        copyAlltoClipboard();

        object misValue = System.Reflection.Missing.Value;
        Excel.Application xlexcel = new Excel.Application();

        xlexcel.DisplayAlerts = false; // Without this you will get two confirm overwrite prompts
        Excel.Workbook xlWorkBook = xlexcel.Workbooks.Add(misValue);
        Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);

        // Format column D as text before pasting results, this was required for my data
        Excel.Range rng = xlWorkSheet.get_Range("D:D").Cells;
        rng.NumberFormat = "@";

        // Paste clipboard results to worksheet range
        Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
        CR.Select();
        xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);

        // For some reason column A is always blank in the worksheet. ¯\_(ツ)_/¯
        // Delete blank column A and select cell A1
        Excel.Range delRng = xlWorkSheet.get_Range("A:A").Cells;
        delRng.Delete(Type.Missing);
        xlWorkSheet.get_Range("A1").Select();

        // Save the excel file under the captured location from the SaveFileDialog
        xlWorkBook.SaveAs(sfd.FileName, Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
        xlexcel.DisplayAlerts = true;
        xlWorkBook.Close(true, misValue, misValue);
        xlexcel.Quit();

        releaseObject(xlWorkSheet);
        releaseObject(xlWorkBook);
        releaseObject(xlexcel);

        // Clear Clipboard and DataGridView selection
        Clipboard.Clear();
        dgvItems.ClearSelection();

        // Open the newly saved excel file
        if (File.Exists(sfd.FileName))
            System.Diagnostics.Process.Start(sfd.FileName);
    }
}

private void copyAlltoClipboard()
{
    dgvItems.SelectAll();
    DataObject dataObj = dgvItems.GetClipboardContent();
    if (dataObj != null)
        Clipboard.SetDataObject(dataObj);
}

private void releaseObject(object obj)
{
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
        obj = null;
    }
    catch (Exception ex)
    {
        obj = null;
        MessageBox.Show("Exception Occurred while releasing object " + ex.ToString());
    }
    finally
    {
        GC.Collect();
    }
}

1
//由于某种原因,工作表中的A列始终为空。正如@Rahul在下面提到的那样,可以通过添加dataGridView1.RowHeadersVisible = false;来避免剪贴板复制中的第一列空白列(这将隐藏行标题列)。如果需要,您可以在剪贴板复制结束时重新显示它。 - El Gucs
@Cornelius 我该如何让复制/导出进程在后台继续运行或使复制异步化? - S.I.J
2
谢谢。这对我有用!我点赞了这个。我在Visual Studio 2017社区版中遇到了麻烦,找不到Mcrosoft.Office.Interop.Excel,但如果你在引用管理器中的COM下添加Microsoft Excel 15.0对象库,它将会把程序集添加到你的项目中。 - BrianMichaels
你好,你的代码非常好用,但是我想在 datagridview 前面粘贴一些文本,比如标题,有什么办法可以实现吗? - Nadjib Bendaoud

11
我没有打算抄袭@Jake和@Cornelius的答案,所以我试着编辑它。但是,它被拒绝了。 无论如何,我唯一需要指出的改进是避免在粘贴后在Excel中添加额外的空白列。添加一行dataGridView1.RowHeadersVisible = false; 可以隐藏所谓的 "行标题",它出现在 DataGridView 的最左侧部分,因此当你执行 dataGridView1.SelectAll();时,它不会被选中并复制到剪贴板中。
private void copyAlltoClipboard()
    {
        //to remove the first blank column from datagridview
        dataGridView1.RowHeadersVisible = false;
        dataGridView1.SelectAll();
        DataObject dataObj = dataGridView1.GetClipboardContent();
        if (dataObj != null)
            Clipboard.SetDataObject(dataObj);
    }
    private void button3_Click_1(object sender, EventArgs e)
    {
        copyAlltoClipboard();
        Microsoft.Office.Interop.Excel.Application xlexcel;
        Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
        Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
        object misValue = System.Reflection.Missing.Value;
        xlexcel = new Excel.Application();
        xlexcel.Visible = true;
        xlWorkBook = xlexcel.Workbooks.Add(misValue);
        xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
        Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
        CR.Select();
        xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);          
    }

1
它对我起作用了。谢谢。它删除了我的DataGridView每行后面的空白行。 - Arani

9
using Excel = Microsoft.Office.Interop.Excel;


private void btnExportExcel_Click(object sender, EventArgs e)
{
    try
    {
        Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
        excel.Visible = true;
        Microsoft.Office.Interop.Excel.Workbook workbook = excel.Workbooks.Add(System.Reflection.Missing.Value);
        Microsoft.Office.Interop.Excel.Worksheet sheet1 = (Microsoft.Office.Interop.Excel.Worksheet)workbook.Sheets[1];
        int StartCol = 1;
        int StartRow = 1;
        int j = 0, i = 0;

        //Write Headers
        for (j = 0; j < dgvSource.Columns.Count; j++)
        {
            Microsoft.Office.Interop.Excel.Range myRange = (Microsoft.Office.Interop.Excel.Range)sheet1.Cells[StartRow, StartCol + j];
            myRange.Value2 = dgvSource.Columns[j].HeaderText;
        }

        StartRow++;

        //Write datagridview content
        for (i = 0; i < dgvSource.Rows.Count; i++)
        {
            for (j = 0; j < dgvSource.Columns.Count; j++)
            {
                try
                {
                    Microsoft.Office.Interop.Excel.Range myRange = (Microsoft.Office.Interop.Excel.Range)sheet1.Cells[StartRow + i, StartCol + j];
                    myRange.Value2 = dgvSource[j, i].Value == null ? "" : dgvSource[j, i].Value;
                }
                catch
                {
                    ;
                }
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}

2
代码不错,但我建议添加这2行代码非常重要:workbook.SaveAs(ExcelFileSavePath); 保存文件,excel.Quit() 杀掉进程,否则当打开 Excel 文件时会出现只读消息。 - solujic

4
最好使用closedxml.codeplex.com库。请参考https://closedxml.codeplex.com/wikipage?title=Adding%20DataTable%20as%20Worksheet&referringTitle=Documentation
var wb = new ClosedXML.Excel.XLWorkbook();
DataTable dt = GetTheDataTable();//Refer documentation


wb.Worksheets.Add(dt);

Response.Clear();
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment;filename=\"FileName.xlsx\"");

using (var ms = new System.IO.MemoryStream()) {
    wb.SaveAs(ms);
    ms.WriteTo(Response.OutputStream);
    ms.Close();
}

Response.End();

3

我喜欢Jake的解决方案。没有标题的问题可以通过以下方式来解决:

xlWorkSheet.Cells[1, 1] = "Header 1";
xlWorkSheet.Cells[1, 2] = "Header 2";
xlWorkSheet.Cells[1, 3] = "Header 3";

当然,这只适用于您事先知道标头应该是什么的情况。

3

Interop很慢而且存在其他问题,使用剪贴板似乎不可扩展。 以下是另外两种实现的方法。

与其使用Excel,直接使用Excel 2007+文件会更快。你可以使用微软的SDK OpenXML (http://openxmldeveloper.org/)。学习OpenXML的最佳方法是下载Productivity工具(http://www.microsoft.com/en-us/download/details.aspx?id=5124),它会获取现有文件并生成创建所需代码。另一个也许更简单的选项是使用ClosedXML(http://closedxml.codeplex.com/)。它似乎更容易使用(查看示例http://closedxml.codeplex.com/wikipage?title=Showcase&referringTitle=Home),但我没有使用过。我相信还有其他封装Excel工作的库。
通过OLEDB处理Excel也是一种选择。这使您可以像使用数据库一样使用Excel。请参见http://www.codeproject.com/Articles/8500/Reading-and-Writing-Excel-using-OLEDBPerformance of OLEDB to read Excel获取示例和更多详细信息。

我会从ClosedXML开始。


你说得没错,但这只是一个链接回答。它实际上并没有提供解决方案,只是一个建议。这更适合作为评论。 - undefined

1
这个答案是针对第一个问题的,即为什么需要花费这么长时间,并提供了一个替代方案来将DataGridView导出到Excel。
MS Office Interop很慢,即使微软也不建议在服务器端使用Interop,并且不能用于导出大型Excel文件。有关更多详细信息,请参见Microsoft的观点why not to use OLE Automation
Interop将Excel文件保存为XLS文件格式(旧的Excel 97-2003文件格式),并且对Office 2003的支持已经结束。Microsoft Excel在Office 2007中发布了XLSX文件格式,并建议使用OpenXML SDK而不是Interop。但是,XLSX文件并不真正快速,并且不能很好地处理大型Excel文件,因为它们基于XML文件格式。这就是为什么Microsoft还发布了XLSB文件格式,该文件格式建议用于大型Excel文件。这是一种二进制格式。因此,最佳且最快的解决方案是保存XLSB文件。
您可以使用此C# Excel library来保存XLSB文件,但它还支持XLS和XLSX文件格式。
请看以下代码示例,作为将DataGridView导出到Excel的替代方案:
// Create a DataSet and add the DataTable of DataGridView 
DataSet dataSet = new DataSet();
dataSet.Tables.Add((DataTable)dataGridView);
//or ((DataTable)dataGridView.DataSource).Copy() to create a copy

// Export Excel file 
ExcelDocument workbook = new ExcelDocument();
workbook.easy_WriteXLSBFile_FromDataSet(filePath, dataSet, 
     new EasyXLS.ExcelAutoFormat(EasyXLS.Constants.Styles.AUTOFORMAT_EASYXLS1), 
     "Sheet1");

如果您还需要导出DataGridView的格式,可以查看此代码示例,了解如何在C#中将DataGridView导出到Excel


1

这是我在GridView中使用的,尝试将其用于您的数据,它完美地工作:

        GridView1.AllowPaging = false;
        GridView1.DataBind();

        StringBuilder sb = new StringBuilder();

        for (int k = 0; k < GridView1.Columns.Count; k++)
        {
            //add separator
            sb.Append(GridView1.Columns[k].HeaderText+";");

        }


        //append new line
        sb.Append("\r\n");
        for (int i = 0; i < GridView1.Rows.Count; i++)
        {
            for (int k = 0; k < GridView1.Columns.Count; k++)
            {
                sb.Append(GridView1.Rows[i].Cells[k].Text+";");
            }
            sb.AppendLine();
        }

0
如果您的 DataGridView 的 RightToLeft 属性设置为 Yes,则数据将被反向复制。因此,您应该使用以下代码来正确地复制数据。
private void copyAlltoClipboard()
{
    dgvItems.RightToLeft = RightToLeft.No;
    dgvItems.SelectAll();
    DataObject dataObj = dgvItems.GetClipboardContent();
    if (dataObj != null)
        Clipboard.SetDataObject(dataObj);
    dgvItems.RightToLeft = RightToLeft.Yes;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接