带格式导出DataGridView可见行列至Excel,含行列合并处理
DataGridView合并行列的Excel导出完善方案
针对你已经实现的可见行列导出、格式保留,以及需要补充的合并行列导出需求,核心是把你在Paint方法里实现的界面合并规则,转换成Excel可识别的合并范围,再应用到导出流程中。以下是具体实现步骤:
1. 抽离合并规则(关键前提)
你之前是通过重写OnPaint实现的界面合并,这部分逻辑只作用于渲染,无法直接被导出代码复用。所以首先要把合并判断逻辑抽成独立方法,收集所有需要合并的单元格范围:
// 用来存储合并范围的实体类 public class MergeRange { public int DgvStartRow { get; set; } public int DgvEndRow { get; set; } public int DgvStartCol { get; set; } public int DgvEndCol { get; set; } public object DisplayValue { get; set; } } // 从DataGridView中提取所有可见行列的合并规则 private List<MergeRange> GetVisibleMergeRanges(DataGridView dgv) { var mergeList = new List<MergeRange>(); for (int rowIdx = 0; rowIdx < dgv.Rows.Count; rowIdx++) { if (!dgv.Rows[rowIdx].Visible) continue; for (int colIdx = 0; colIdx < dgv.Columns.Count; colIdx++) { if (!dgv.Columns[colIdx].Visible) continue; // 这里替换成你在OnPaint里判断合并的实际逻辑 // 示例:向下合并相同值的单元格 int mergeRowCount = 1; while (rowIdx + mergeRowCount < dgv.Rows.Count && dgv.Rows[rowIdx + mergeRowCount].Visible && dgv.Rows[rowIdx].Cells[colIdx].Value?.ToString() == dgv.Rows[rowIdx + mergeRowCount].Cells[colIdx].Value?.ToString()) { mergeRowCount++; } // 示例:向右合并相同值的单元格 int mergeColCount = 1; while (colIdx + mergeColCount < dgv.Columns.Count && dgv.Columns[colIdx + mergeColCount].Visible && dgv.Rows[rowIdx].Cells[colIdx].Value?.ToString() == dgv.Rows[rowIdx].Cells[colIdx + mergeColCount].Value?.ToString()) { mergeColCount++; } // 只处理需要合并的情况,且避免重复添加已被合并的单元格 if ((mergeRowCount > 1 || mergeColCount > 1) && !mergeList.Any(r => r.DgvStartRow <= rowIdx && rowIdx <= r.DgvEndRow && r.DgvStartCol <= colIdx && colIdx <= r.DgvEndCol)) { mergeList.Add(new MergeRange { DgvStartRow = rowIdx, DgvEndRow = rowIdx + mergeRowCount - 1, DgvStartCol = colIdx, DgvEndCol = colIdx + mergeColCount - 1, DisplayValue = dgv.Rows[rowIdx].Cells[colIdx].Value }); } } } return mergeList; }
2. 在XLWorkbook导出流程中应用合并规则
在你现有导出可见行列、保留对齐格式的代码基础上,添加合并逻辑。重点要注意:DataGridView的行列是从0开始,Excel是从1开始,且要跳过不可见行列的偏移,否则合并范围会错位:
private void ExportDgvToExcel(DataGridView dgv) { using (var workbook = new XLWorkbook()) { var worksheet = workbook.Worksheets.Add("导出数据"); int excelRow = 1; int excelCol = 1; // --- 1. 导出可见表头(保留格式)--- foreach (DataGridViewColumn col in dgv.Columns) { if (!col.Visible) continue; worksheet.Cell(excelRow, excelCol).Value = col.HeaderText; // 对齐格式映射 worksheet.Cell(excelRow, excelCol).Style.Alignment.Horizontal = (XLAlignmentHorizontalValues)col.DefaultCellStyle.Alignment; worksheet.Cell(excelRow, excelCol).Style.Alignment.Vertical = (XLAlignmentVerticalValues)col.DefaultCellStyle.Alignment; excelCol++; } excelRow++; // --- 2. 导出可见数据行(保留格式)--- foreach (DataGridViewRow row in dgv.Rows) { if (!row.Visible) continue; excelCol = 1; foreach (DataGridViewColumn col in dgv.Columns) { if (!col.Visible) continue; var cellVal = row.Cells[col.Index].Value; worksheet.Cell(excelRow, excelCol).Value = cellVal?.ToString() ?? string.Empty; // 单元格对齐格式映射 worksheet.Cell(excelRow, excelCol).Style.Alignment.Horizontal = (XLAlignmentHorizontalValues)row.Cells[col.Index].Style.Alignment; worksheet.Cell(excelRow, excelCol).Style.Alignment.Vertical = (XLAlignmentVerticalValues)row.Cells[col.Index].Style.Alignment; excelCol++; } excelRow++; } // --- 3. 应用合并规则到Excel --- var mergeRanges = GetVisibleMergeRanges(dgv); foreach (var range in mergeRanges) { // 计算Excel中合并范围的起始行(跳过不可见行) int excelStartRow = 1; // 表头占1行 for (int i = 0; i <= range.DgvStartRow; i++) { if (dgv.Rows[i].Visible) excelStartRow++; } // 计算Excel中合并范围的结束行 int excelEndRow = excelStartRow; for (int i = range.DgvStartRow + 1; i <= range.DgvEndRow; i++) { if (dgv.Rows[i].Visible) excelEndRow++; } // 计算Excel中合并范围的起始列(跳过不可见列) int excelStartCol = 0; for (int i = 0; i <= range.DgvStartCol; i++) { if (dgv.Columns[i].Visible) excelStartCol++; } // 计算Excel中合并范围的结束列 int excelEndCol = excelStartCol; for (int i = range.DgvStartCol + 1; i <= range.DgvEndCol; i++) { if (dgv.Columns[i].Visible) excelEndCol++; } // 执行合并,并设置合并后单元格的显示值 if (excelStartRow <= excelEndRow && excelStartCol <= excelEndCol) { worksheet.Range(excelStartRow, excelStartCol, excelEndRow, excelEndCol).Merge(); worksheet.Cell(excelStartRow, excelStartCol).Value = range.DisplayValue?.ToString() ?? string.Empty; } } // --- 4. 保存文件并删除空白行 --- var saveDialog = new SaveFileDialog { Filter = "Excel文件 (*.xlsx)|*.xlsx" }; if (saveDialog.ShowDialog() == DialogResult.OK) { workbook.SaveAs(saveDialog.FileName); // Aspose.Cells删除空白行 var asposeWorkbook = new Aspose.Cells.Workbook(saveDialog.FileName); var asposeSheet = asposeWorkbook.Worksheets[0]; // 倒序删除空白行,避免索引错乱 for (int i = asposeSheet.Cells.MaxRow; i >= 0; i--) { if (asposeSheet.Cells.Rows[i].IsBlank) { asposeSheet.Cells.DeleteRow(i); } } asposeWorkbook.Save(saveDialog.FileName); } } }
3. 核心注意事项
- 合并规则的准确性:如果你的合并逻辑是基于业务数据(比如分组字段相同),直接从数据源提取合并规则会比从DataGridView界面提取更高效,也能避免界面渲染带来的误差。
- 避免重复合并:提取合并规则时一定要判断当前单元格是否已经被包含在其他合并范围内,否则会导致Excel合并冲突。
- 索引偏移处理:必须严格处理可见行列的索引转换,否则合并范围会和实际导出的行列不匹配。
内容的提问来源于stack exchange,提问作者LiterallyGutsFromBerserk
相关产品推荐
相关产品推荐

