我解决了上述问题。我必须说使用 OpenXML 有点令人沮丧,但我对最终结果感到满意。
我决定 - 基于许多 OpenXML 主题 - 通过提供完整的可用代码来扩展答案,而不仅仅是我通常在许多网站上遇到的示例。
我的基本要求是将 Datagridview 数据导出到 Excel 文件中,具有正确的单元格格式和比我们当前使用的 Interop 解决方案更快的导出速度。下面的代码也可以与 Datatable 或 Dataset 一起使用,只需稍作修改。我还添加了一些其他功能,我认为这些功能应该记录在案,因为这是大多数程序员在 Excel 中需要的,但不幸的是它们不是。
我不会深入研究所有内容,因为我在做这一切时已经有些头疼了,所以让我们切入正题。以下完整代码的结果是 Excel 文件,其中包含从 Datagridview 导出的数据和:
- 列名与 Datagridview 标题相同,以粗体显示;
- 将默认字体 »Calibri« 更改为 »Arial«;
- 单元格格式基于 Datatable 中的实际数据(日期、数字和字符串),具有所需格式;
- 保存文件对话框提示;
- 自动调整列;
正如许多其他人所说,OpenXML 中的顺序非常重要。这几乎适用于所有事情——当您创建文档或样式时。因此,您在此处看到的所有内容在 Office 2016 中对我来说都可以正常工作,但是如果您在 Excel 中进行一些线路混合,最终会很快出现某种奇怪的错误……正如所承诺的,这是我的完整代码:
public void Export_to_Excel(DataGridView dgv, string file_name)
{
String file_path= Environment.GetFolderPath(Environment.SpecialFolder.Desktop).ToString() + "\\" +file_name + ".xlsx";
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.InitialDirectory = Convert.ToString(Environment.SpecialFolder.Desktop);
saveFileDialog.Filter = "Excel Workbook |*.xlsx";
saveFileDialog.Title = "Save as";
saveFileDialog.FileName = file_name;
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
file_path = saveFileDialog.FileName;
}
else
{
return;
}
using (var workbook = SpreadsheetDocument.Create(file_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();
//Autofit comes first – we calculate width of columns based on data
sheetPart.Worksheet = new Worksheet();
sheetPart.Worksheet.Append(AutoFit_Columns(dgv));
sheetPart.Worksheet.Append(sheetData);
//Adding styles to worksheet
Worksheet_Style(workbook);
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(); //Adding column headers
for (int col = 0; col < dgv.ColumnCount; col++)
{
Cell cell = new Cell
{
DataType = CellValues.String,
CellValue = new CellValue(dgv.Columns[col].HeaderText),
StyleIndex = 1// bold font
};
headerRow.AppendChild(cell);
}
// Add the row values to the excel sheet
sheetData.AppendChild(headerRow);
for (int row = 0; row < dgv.RowCount; row++)
{
Row newRow = new Row();
for (int col = 0; col < dgv.ColumnCount; col++)
{
Cell cell = new Cell();
//Checking types of data
// I had problems here with Number format, I just can't set It to a
// Datatype=CellValues.Number. If someone knows answer please let me know. However, Date format strangely works fine with Number datatype ?
// Also important – whatever format you define in creating stylesheets, you have to insert value of same kind in string here – for CellValues !
// I used cell formating as I needed, for something else just change Worksheet_Style method to your needs
if (dgv.Columns[col].ValueType == typeof(decimal)) //numbers
{
cell.DataType = new EnumValue<CellValues>(CellValues.String);
cell.CellValue = new CellValue(((decimal)dgv.Rows[row].Cells[col].Value).ToString("#,##0.00"));
cell.StyleIndex = 3;
}
else if (dgv.Columns[col].ValueType == typeof(DateTime)) //dates
{
cell.DataType = new EnumValue<CellValues>(CellValues.Number);
cell.CellValue = new CellValue(((DateTime)dgv.Rows[row].Cells[col].Value).ToOADate().ToString(CultureInfo.InvariantCulture));
cell.StyleIndex = 2;
}
Else // strings
{
cell.DataType = new EnumValue<CellValues>(CellValues.String);
cell.CellValue = new CellValue(dgv.Rows[row].Cells[col].Value.ToString());
cell.StyleIndex = 0;
}
newRow.AppendChild(cell);
}
sheetData.AppendChild(newRow);
}
}
}
private static WorkbookStylesPart Worksheet_Style (SpreadsheetDocument document)
{
WorkbookStylesPart create_style = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();
Stylesheet workbookstylesheet = new Stylesheet();
DocumentFormat.OpenXml.Spreadsheet.Font font0 = new DocumentFormat.OpenXml.Spreadsheet.Font(); // Default font
FontName arial = new FontName() { Val = "Arial" };
FontSize size = new FontSize() { Val = 10 };
font0.Append(arial);
font0.Append(size);
DocumentFormat.OpenXml.Spreadsheet.Font font1 = new DocumentFormat.OpenXml.Spreadsheet.Font(); // Bold font
Bold bold = new Bold();
font1.Append(bold);
// Append both fonts
Fonts fonts = new Fonts();
fonts.Append(font0);
fonts.Append(font1);
//Append fills - a must, in my case just default
Fill fill0 = new Fill();
Fills fills = new Fills();
fills.Append(fill0);
// Append borders - a must, in my case just default
Border border0 = new Border(); // Default border
Borders borders = new Borders();
borders.Append(border0);
// CellFormats
CellFormats cellformats = new CellFormats();
CellFormat cellformat0 = new CellFormat() { FontId = 0, FillId = 0, BorderId = 0 }; // Default style : Mandatory | Style ID =0
CellFormat bolded_format = new CellFormat() { FontId = 1 }; // Style with Bold text ; Style ID = 1
CellFormat date_format = new CellFormat() { BorderId = 0, FillId = 0, FontId = 0, NumberFormatId = 14, FormatId = 0, ApplyNumberFormat = true };
CellFormat number_format = new CellFormat() { BorderId = 0, FillId = 0, FontId = 0, NumberFormatId = 4, FormatId = 0, ApplyNumberFormat = true }; // format like "#,##0.00"
cellformats.Append(cellformat0);
cellformats.Append(bolded_format);
cellformats.Append(date_format);
cellformats.Append(number_format);
// Append everyting to stylesheet - Preserve the ORDER !
workbookstylesheet.Append(fonts);
workbookstylesheet.Append(fills);
workbookstylesheet.Append(borders);
workbookstylesheet.Append(cellformats);
//Save style for finish
create_style.Stylesheet = workbookstylesheet;
create_style.Stylesheet.Save();
return create_style;
}
private Columns AutoFit_Columns(DataGridView dgv)
{
Columns cols = new Columns();
int Excel_column=0;
DataTable dt = new DataTable();
dt = (DataTable)dgv.DataSource;
for (int col = 0; col < dgv.ColumnCount; col++)
{
double max_width = 14.5f; // something like default Excel width, I'm not sure about this
//We search for longest string in each column and convert that into double to get desired width
string longest_string = dt.AsEnumerable()
.Select(row => row[col].ToString())
.OrderByDescending(st => st.Length).FirstOrDefault();
double cell_width = GetWidth(new System.Drawing.Font("Arial", 10), longest_string);
if (cell_width > max_width)
{
max_width = cell_width;
}
if (col == 0) //first column of Datagridview is index 0, but there is no 0 index of column in Excel, careful with that !!!
{
Excel_column = 1;
}
//now append column to worksheet, calculations done
Column c = new Column() { Min = Convert.ToUInt32(Excel_column), Max = Convert.ToUInt32(Excel_column), Width = max_width, CustomWidth = true };
cols.Append(c);
Excel_column++;
}
return cols;
}
private static double GetWidth(System.Drawing.Font stringFont, string text)
{
// This formula calculates width. For better desired outputs try to change 0.5M to something else
Size textSize = TextRenderer.MeasureText(text, stringFont);
double width = (double)(((textSize.Width / (double)7) * 256) - (128 / 7)) / 256;
width = (double)decimal.Round((decimal)width + 0.5M, 2);
return width;
}
方法,在我的例子中,从 .dll 可以很容易地调用,例如:
Export_to_Excel(my_dgv, »test_file«)
代码中一些东西的简短解释:
1.) 样式:我可以有很多选择,但对我来说这是最简单的方法。当您需要更难的东西时,请尽量不要忘记订单在这里也很重要。并且附加字体、填充和边框是必要的。
2.) Autofit:我无法相信为什么没有记录在案,我认为 OpenXML 默认情况下应该有一些方法。无论如何,我通过使用 LINQ 并在here 的帮助下解决了这个问题。我希望作者不介意,但应该有人大声说出来:)
最后,我的测试结果和与 Interop 相比的优势/劣势。我在 Excel 2016 上测试了 20 万行数据:
互操作
优点:
- 更容易编码(在我看来)具有许多内置功能,例如(当然)Autofit;
- 您实际上可以创建尚未完全保存到磁盘的 Excel 文件(对象);
缺点:
- 与任何其他库(如 OpenXML)相比,速度较慢,但我可以将 3 分钟缩短到 2 分钟;
- 我还注意到大数据消耗大量内存,尽管我的互操作代码已经非常优化;
OpenXML
- 20 秒内导出数据(具有自动调整功能和所有样式);
优点:
- 比 Interop 快得多,而且我认为我的“垃圾”代码可以得到更多优化(如果您愿意,可以提供帮助);
缺点:
- 编码,不明显? :)
- 虽然 OpenXML 提供了两种方法,即 SAX 或 DOM 方法,但内存消耗比 Interop 更高。 SAX 甚至比提供的代码更快,几乎没有内存消耗如果您直接从 DataReader 将数据写入 Excel ,但编码花费了我很多时间;
我希望没有人会生气,因为我实际上所做的是将许多站点的点点滴滴放入真正有用的东西中,而不是编写没人理解的复杂示例。如果有人愿意改进以上任何内容,我将不胜感激。我不是完美的,更多的头脑在一起通常最终会为每个人形成一个更好的解决方案:)