OpenXML - 从 Datagridview 导出时更改 Excel 单元格格式(日期和数字)

Luc*_*y82 2 c# openxml

我使用 OpenXML 将 Datagridview 导出到 Excel。如果我使用 CellValues.String 导出单元格,则一切工作正常,Excel 文件中不会出现任何错误,但我需要的是将所有日期和数字数据正确转换为相应的单元格格式。我尝试使用内置格式(不是自定义格式)来更改单元格格式,但后来我的 Excel 损坏了。

这是我到目前为止所尝试的:

  public void Export_to_Excel(DataGridView dgv, string path)
    {
        using (var workbook = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook))
        {
            var workbookPart = workbook.AddWorkbookPart();

            workbook.WorkbookPart.Workbook = new Workbook();
            workbook.WorkbookPart.Workbook.Sheets = new Sheets();

            var sheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
            var sheetData = new SheetData();
            sheetPart.Worksheet = new Worksheet(sheetData);

            Sheets sheets = workbook.WorkbookPart.Workbook.GetFirstChild<Sheets>();
            string relationshipId = workbook.WorkbookPart.GetIdOfPart(sheetPart);

            uint sheetId = 1;
            if (sheets.Elements<Sheet>().Count() > 0)
            {
                sheetId =
                    sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
            }

            Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = "List "+ sheetId};
            sheets.Append(sheet);

            Row headerRow = new Row();

            // Construct column names 
            List<String> columns = new List<string>();
            foreach (DataGridViewColumn column in dgv.Columns)
            {
                columns.Add(column.Name);

                Cell cell = new Cell
                {
                    DataType = CellValues.String,
                    CellValue = new CellValue(column.HeaderText)
                };
                headerRow.AppendChild(cell);
            }

            // Add the row values to the excel sheet 
            sheetData.AppendChild(headerRow);

            foreach (DataGridViewRow dsrow in dgv.Rows)
            {
                Row newRow = new Row();
                foreach (String col in columns)
                {

                    CellValues cell_type = new CellValues();
                    string cell_value = "";
                    UInt32 style_index;
                    if (dsrow.Cells[col].ValueType == typeof(decimal)) //numbers
                    {
                        cell_type = CellValues.Number;
                        cell_value = ((decimal)dsrow.Cells[col].Value).ToString();
                        style_index = 4; //should be #,##0.00
                    }
                    else if (dsrow.Cells[col].ValueType == typeof(DateTime)) //dates
                    {
                        cell_type = CellValues.String;
                        cell_value = ((DateTime)dsrow.Cells[col].Value).ToString("dd.mm.yyyy");
                        style_index =0; //should be General
                    }
                    else
                    {
                        cell_type = CellValues.String;
                        cell_value = dsrow.Cells[col].Value.ToString();
                        index_stila = 0; //should be General
                    }

                    Cell cell = new Cell();
                    cell.DataType = new EnumValue<CellValues>(cell_type);
                    cell.CellValue = new CellValue(cell_value);
                    cell.StyleIndex = style_index;
                    newRow.AppendChild(cell);
                }

                sheetData.AppendChild(newRow);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

所以基本上,我想要的是正确设置这些单元格的格式。在上面的代码中,我仅尝试了数字格式,但我也需要相同的日期格式。这里还有OpenXML 内置样式的链接。

Luc*_*y82 8

我解决了上面的问题。我必须说,使用 OpenXML 有点令人沮丧,但我对最终结果很满意。

\n\n

我根据许多 OpenXML 主题决定 \xe2\x80\x93 - 通过提供完整的可用代码来扩展答案,而不仅仅是我通常在许多网站上遇到的示例。

\n\n

我的基本要求是将 Datagridview 数据导出到 Excel 文件中,具有正确的单元格格式,并且导出速度比我们当前使用的 Interop 解决方案更快。下面的代码也可以与数据表或数据集一起使用,只需稍作修改即可。我还添加了一些其他功能,我认为这些功能应该记录为大多数程序员在 Excel 中需要的功能,但不幸的是它们不是。

\n\n

我不会深入探讨所有内容,因为我在做所有这些事情时已经感到有些头痛,所以让我们切入正题。下面完整代码的结果是 Excel 文件,其中包含从 Datagridview 导出的数据:

\n\n
    \n
  • 列名称与 Datagridview 标题相同并以粗体显示;
  • \n
  • 将默认字体 \xc2\xbbCalibri\xc2\xab 更改为 \xc2\xbbArial\xc2\xab;
  • \n
  • 基于数据表中的实际数据(日期、数字和字符串)的单元格格式以及所需的格式;
  • \n
  • 保存文件对话框提示;
  • \n
  • 自动调整列;
  • \n
\n\n

正如许多其他人所说,OpenXML 中的顺序非常重要。当您创建文档或设置样式时,这几乎适用于所有 \xe2\x80\x93 。所以你在这里看到的一切在 Office 2016 中对我来说都工作得很好,但是如果你做了一些行混合,你最终会很快在 Excel\xe2\x80\xa6 中出现一些奇怪的错误正如所承诺的,这里是我的完整代码:

\n\n
public void Export_to_Excel(DataGridView dgv, string file_name)\n{\n  String file_path= Environment.GetFolderPath(Environment.SpecialFolder.Desktop).ToString() + "\\\\" +file_name + ".xlsx";\n\n  SaveFileDialog saveFileDialog = new SaveFileDialog();\n  saveFileDialog.InitialDirectory = Convert.ToString(Environment.SpecialFolder.Desktop);\n  saveFileDialog.Filter = "Excel Workbook |*.xlsx";\n  saveFileDialog.Title = "Save as";\n  saveFileDialog.FileName = file_name;\n  if (saveFileDialog.ShowDialog() == DialogResult.OK)\n  {\n    file_path = saveFileDialog.FileName;                  \n  }\n  else\n  {\n    return;\n  }\n\n using (var workbook = SpreadsheetDocument.Create(file_path, SpreadsheetDocumentType.Workbook))\n {\n    var workbookPart = workbook.AddWorkbookPart();\n    workbook.WorkbookPart.Workbook = new Workbook();\n    workbook.WorkbookPart.Workbook.Sheets = new Sheets();\n\n    var sheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();\n    var sheetData = new SheetData();\n\n     //Autofit comes first \xe2\x80\x93 we calculate width of columns based on data\n     sheetPart.Worksheet = new Worksheet();\n     sheetPart.Worksheet.Append(AutoFit_Columns(dgv));\n     sheetPart.Worksheet.Append(sheetData);\n\n     //Adding styles to worksheet\n     Worksheet_Style(workbook);\n\n     Sheets sheets = workbook.WorkbookPart.Workbook.GetFirstChild<Sheets>();\n     string relationshipId = workbook.WorkbookPart.GetIdOfPart(sheetPart);\n\n     uint sheetId = 1;\n     if (sheets.Elements<Sheet>().Count() > 0)\n     {\n       sheetId = sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;\n     }\n\n     Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = "List " + sheetId };\n      sheets.Append(sheet);\n\n      Row headerRow = new Row(); //Adding column headers\n\n      for (int col = 0; col < dgv.ColumnCount; col++)\n      {\n         Cell cell = new Cell\n         {\n             DataType = CellValues.String,\n             CellValue = new CellValue(dgv.Columns[col].HeaderText),\n             StyleIndex = 1// bold font\n         };\n         headerRow.AppendChild(cell);\n       }\n\n       // Add the row values to the excel sheet \n       sheetData.AppendChild(headerRow);\n\n       for (int row = 0; row < dgv.RowCount; row++)\n       {\n          Row newRow = new Row();\n\n          for (int col = 0; col < dgv.ColumnCount; col++)\n          {\n              Cell cell = new Cell();\n\n              //Checking types of data\n              // I had problems here with Number format, I just can\'t set It to a\n              // Datatype=CellValues.Number. If someone knows answer please let me know. However, Date format strangely works fine with Number datatype ?\n              // Also important \xe2\x80\x93 whatever format you define in creating stylesheets, you have to insert value of same kind in string here \xe2\x80\x93 for CellValues !\n              // I used cell formating as I needed, for something else just change Worksheet_Style method to your needs\n              if (dgv.Columns[col].ValueType == typeof(decimal)) //numbers\n              {\n                 cell.DataType = new EnumValue<CellValues>(CellValues.String);\n                 cell.CellValue = new CellValue(((decimal)dgv.Rows[row].Cells[col].Value).ToString("#,##0.00"));\n                  cell.StyleIndex = 3;\n               }\n               else if (dgv.Columns[col].ValueType == typeof(DateTime)) //dates\n               {\n                  cell.DataType = new EnumValue<CellValues>(CellValues.Number);\n                  cell.CellValue = new CellValue(((DateTime)dgv.Rows[row].Cells[col].Value).ToOADate().ToString(CultureInfo.InvariantCulture));\n                  cell.StyleIndex = 2;\n                }\n                Else // strings\n                {\n                  cell.DataType = new EnumValue<CellValues>(CellValues.String);\n                  cell.CellValue = new CellValue(dgv.Rows[row].Cells[col].Value.ToString());\n                  cell.StyleIndex = 0;\n          }\n                 newRow.AppendChild(cell);\n                }\n                    sheetData.AppendChild(newRow);\n                }\n            }\n\n }\n\n        private static WorkbookStylesPart Worksheet_Style (SpreadsheetDocument document)\n        {\n            WorkbookStylesPart create_style = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();\n            Stylesheet workbookstylesheet = new Stylesheet();\n\n            DocumentFormat.OpenXml.Spreadsheet.Font font0 = new DocumentFormat.OpenXml.Spreadsheet.Font(); // Default font\n            FontName arial = new FontName() { Val = "Arial" };\n            FontSize size = new FontSize() { Val = 10 };\n            font0.Append(arial);\n            font0.Append(size);\n\n\n            DocumentFormat.OpenXml.Spreadsheet.Font font1 = new DocumentFormat.OpenXml.Spreadsheet.Font(); // Bold font\n            Bold bold = new Bold();\n            font1.Append(bold);\n\n            // Append both fonts\n            Fonts fonts = new Fonts();     \n            fonts.Append(font0);\n            fonts.Append(font1);\n\n            //Append fills - a must, in my case just default\n            Fill fill0 = new Fill();        \n            Fills fills = new Fills();      \n            fills.Append(fill0);\n\n            // Append borders - a must, in my case just default\n            Border border0 = new Border();     // Default border\n            Borders borders = new Borders();    \n            borders.Append(border0);\n\n            // CellFormats\n            CellFormats cellformats = new CellFormats();\n\n            CellFormat cellformat0 = new CellFormat() { FontId = 0, FillId = 0, BorderId = 0 }; // Default style : Mandatory | Style ID =0\n            CellFormat bolded_format = new CellFormat() { FontId = 1 };  // Style with Bold text ; Style ID = 1\n            CellFormat date_format = new CellFormat() { BorderId = 0, FillId = 0, FontId = 0, NumberFormatId = 14, FormatId = 0, ApplyNumberFormat = true };\n            CellFormat number_format = new CellFormat() { BorderId = 0, FillId = 0, FontId = 0, NumberFormatId = 4, FormatId = 0, ApplyNumberFormat = true }; // format like "#,##0.00"\n\n            cellformats.Append(cellformat0);\n            cellformats.Append(bolded_format);\n            cellformats.Append(date_format);\n            cellformats.Append(number_format);\n\n            // Append everyting to stylesheet  - Preserve the ORDER !\n            workbookstylesheet.Append(fonts);\n            workbookstylesheet.Append(fills);\n            workbookstylesheet.Append(borders);\n            workbookstylesheet.Append(cellformats);\n\n            //Save style for finish\n            create_style.Stylesheet = workbookstylesheet;\n            create_style.Stylesheet.Save();\n\n            return create_style;\n        }\n\n\n        private Columns AutoFit_Columns(DataGridView dgv)\n        {\n            Columns cols = new Columns();\n            int Excel_column=0;\n\n            DataTable dt = new DataTable();\n            dt = (DataTable)dgv.DataSource;\n\n            for (int col = 0; col < dgv.ColumnCount; col++)\n            {\n                double max_width = 14.5f; // something like default Excel width, I\'m not sure about this\n\n                //We search for longest string in each column and convert that into double to get desired width \n                string longest_string = dt.AsEnumerable()\n                     .Select(row => row[col].ToString())\n                     .OrderByDescending(st => st.Length).FirstOrDefault();\n\n                double cell_width = GetWidth(new System.Drawing.Font("Arial", 10), longest_string);\n\n                if (cell_width > max_width)\n                {\n                    max_width = cell_width;\n                }\n\n                if (col == 0) //first column of Datagridview is index 0, but there is no 0 index of column in Excel, careful with that !!!\n                {\n                    Excel_column = 1;\n                }\n\n                //now append column to worksheet, calculations done\n                Column c = new Column() { Min = Convert.ToUInt32(Excel_column), Max = Convert.ToUInt32(Excel_column), Width = max_width, CustomWidth = true };\n                cols.Append(c);\n\n                Excel_column++;\n            }\n            return cols;\n        }\n\n        private static double GetWidth(System.Drawing.Font stringFont, string text)\n        {\n            // This formula calculates width. For better desired outputs try to change 0.5M to something else\n\n            Size textSize = TextRenderer.MeasureText(text, stringFont);\n            double width = (double)(((textSize.Width / (double)7) * 256) - (128 / 7)) / 256;\n            width = (double)decimal.Round((decimal)width + 0.5M, 2);\n\n            return width;\n        }\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我的例子中,可以从 .dll 轻松调用方法,如下所示:

\n\n
Export_to_Excel(my_dgv, \xc2\xbbtest_file\xc2\xab)\n
Run Code Online (Sandbox Code Playgroud)\n\n

代码中一些内容的简短解释:

\n\n

1.)风格:我可以有很多选择,但这对我来说是最简单的方法。当您需要更难的东西时,请不要忘记这里的顺序也很重要。并且附加字体、填充和边框是必要的。

\n\n

2.)自动调整:我无法相信为什么还没有记录下来,我的观点是 OpenXML 默认情况下应该有一些方法。不管怎样,我通过使用 LINQ 并在此处的帮助下解决了这个问题。我希望作者不介意,但应该有人大声说出来:)

\n\n

最后,我的测试结果以及与 Interop 相比的优点/缺点。我在 Excel 2016 上测试了 20 万行数据:

\n\n

互操作性

\n\n
    \n
  • 近3分钟导出数据;
  • \n
\n\n

优点:

\n\n
    \n
  • 更容易编码(在我看来)有很多内置功能,例如(当然)自动调整;
  • \n
  • 您实际上可以创建尚未保存到磁盘的 Excel 文件(对象);
  • \n
\n\n

缺点:

\n\n
    \n
  • 与任何其他库(例如 OpenXML)相比,速度很慢,尽管我可以将 3 分钟减少到 2 分钟;
  • \n
  • 我还注意到大数据上的巨大内存消耗,即使我的互操作代码经过了相当优化;
  • \n
\n\n

开放XML

\n\n
    \n
  • 20 秒内导出数据(具有自动调整功能和所有样式);
  • \n
\n\n

优点:

\n\n
    \n
  • 比 Interop 快得多,而且我认为我的 \xc2\xbbrubbish\xc2\xab 代码可以更加优化(如果你关心的话,你可以帮忙);
  • \n
\n\n

缺点:

\n\n
    \n
  • 编码,不明显吗?:)
  • \n
  • 尽管 OpenXML 提供了两种方法(即 SAX 或 DOM 方法),但内存消耗比 Interop 更高。SAX 甚至比提供的代码更快,几乎没有内存消耗如果您直接从 DataReader 将数据写入 Excel,但编码花了我很多时间;
  • \n
\n\n

我希望没有人会生气,因为我实际上所做的是将来自许多站点的零碎内容放入实际有用的东西中,而不是编写没有人理解的复杂示例。如果有人愿意改进以上任何内容,我将非常感激。我并不完美,更多的头脑通常会最终为每个人形成更好的解决方案:)

\n