C# RDLC选Microsoft Print to PDF时如何合并所有页为单个PDF
问题背景
现有基于C#开发的程序可读取CSV文件数据渲染RDLC报表,连接常规实体打印机执行打印时功能运行正常。
现存问题
当用户选择「Microsoft Print to PDF」作为输出打印机时,系统会逐页触发打印流程、逐页保存独立PDF文件;若待打印页数达到1000页,就会生成1000个独立PDF文件,使用效率极低。
预期需求
仅当用户选中Microsoft Print to PDF打印机时,将所有待打印页面合并输出为1个PDF文件;选择其他实体或虚拟打印机时,保持原有打印逻辑不变。
当前实现说明
LocalReportExtensioncs.cs为打印扩展类,当前该类逻辑会为每个待打印页面执行1次,若存在1000个待打印页面则会执行1000次,对应源码如下:
public static void PrintToPrinter(this LocalReport report) { var pageSettings = new PageSettings(); pageSettings.Color = false; pageSettings.PaperSize = report.GetDefaultPageSettings().PaperSize; pageSettings.Landscape = report.GetDefaultPageSettings().IsLandscape; pageSettings.Margins = report.GetDefaultPageSettings().Margins; pageSettings.Landscape = true; Print(report, pageSettings); } public static void Print(this LocalReport report, PageSettings pageSettings) { string deviceInfo = $@"<DeviceInfo> <OutputFormat>EMF</OutputFormat> <PageWidth>{pageSettings.PaperSize.Width * 100}in</PageWidth> <PageHeight>{pageSettings.PaperSize.Height * 100}in</PageHeight> <MarginTop>{pageSettings.Margins.Top * 100}in</MarginTop> <MarginLeft>{pageSettings.Margins.Left * 100}in</MarginLeft> <MarginRight>{pageSettings.Margins.Right * 100}in</MarginRight> <MarginBottom>{pageSettings.Margins.Bottom * 100}in</MarginBottom> </DeviceInfo>"; Warning[] warnings; var streams = new List<Stream>(); var currentPageIndex = 0; report.Render("Image", deviceInfo, (name, fileNameExtension, encoding, mimeType, willSeek) => { var stream = new MemoryStream(); streams.Add(stream); return stream; }, out warnings); foreach (Stream stream in streams) stream.Position = 0; if (streams.Equals(null) || streams.Count == 0) throw new Exception("Error: no stream to print."); var printDocument = new PrintDocument(); printDocument.DocumentName = "default"; var printerController = new StandardPrintController(); printDocument.PrintController = printerController; printDocument.DefaultPageSettings.Color = false; printDocument.DefaultPageSettings = pageSettings; DialogResult result = DialogResult.Retry; while (!printDocument.PrinterSettings.IsValid) { result = MessageBox.Show("Error, Default printer hasnt been selected", "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); if (result == DialogResult.Cancel) { Environment.Exit(0); } } printDocument.PrintPage += (sender, e) => { Metafile pageImage = new Metafile(streams[currentPageIndex]); Rectangle adjustedRect = new Rectangle( e.PageBounds.Left - (int)e.PageSettings.HardMarginX, e.PageBounds.Top - (int)e.PageSettings.HardMarginY, e.PageBounds.Width, e.PageBounds.Height); e.Graphics.FillRectangle(Brushes.White, adjustedRect); e.Graphics.DrawImage(pageImage, adjustedRect); currentPageIndex++; e.HasMorePages = (currentPageIndex < streams.Count); e.Graphics.DrawRectangle(Pens.Red, adjustedRect); }; printDocument.EndPrint += (Sender, e) => { if (streams != null) { foreach (Stream stream in streams) stream.Close(); streams = null; } }; printDocument.PrinterSettings.PrinterName = Properties.Settings.Default.PrinterName; printDocument.Print(); } }
main.cs为报表数据加载与打印调用入口,当前通过循环逐次调用打印方法,对应源码如下:
localReport = AddToLocalReport(localReport); localReport.ReportPath = Application.StartupPath + "\\RDLCreport.rdlc"; for (int i = 1; i < printCounter + 1; i++) { if (Properties.Settings.Default.canPrint == true) { localReport.PrintToPrinter(); } }
解决方案
问题核心原因有两个:
- 入口代码按页循环单独调用打印方法,每调用一次就生成一个独立打印任务
- Microsoft Print to PDF虚拟打印机默认逻辑为每个独立打印任务生成一个单独PDF文件,不会自动合并同来源任务
修复逻辑:加打印机类型分支判断,选中Microsoft Print to PDF时把所有待打印页面合并到同一个打印任务里一次性提交,其他打印机完全保留原有逻辑,不影响现有功能。
具体代码修改
第一步:改造打印扩展类,新增批量打印重载
在LocalReportExtensioncs.cs中保留原有单份打印方法兼容旧逻辑,新增批量打印方法,将所有页面汇总到同一个打印任务提交:
// 原有单份打印方法保留,供非PDF打印场景使用 public static void PrintToPrinter(this LocalReport report) { var pageSettings = new PageSettings(); pageSettings.Color = false; pageSettings.PaperSize = report.GetDefaultPageSettings().PaperSize; pageSettings.Landscape = report.GetDefaultPageSettings().IsLandscape; pageSettings.Margins = report.GetDefaultPageSettings().Margins; pageSettings.Landscape = true; Print(report, pageSettings); } // 新增批量打印重载,接收所有待打印报表实例,合并为单个打印任务 public static void BatchPrintToPrinter(this List<LocalReport> reports) { if (reports == null || reports.Count == 0) throw new Exception("无待打印报表数据"); var allPageStreams = new List<Stream>(); var firstReport = reports[0]; var pageSettings = new PageSettings(); pageSettings.Color = false; pageSettings.PaperSize = firstReport.GetDefaultPageSettings().PaperSize; pageSettings.Landscape = firstReport.GetDefaultPageSettings().IsLandscape; pageSettings.Margins = firstReport.GetDefaultPageSettings().Margins; pageSettings.Landscape = true; // 逐份渲染报表,汇总所有页面的EMF流 foreach (var report in reports) { string deviceInfo = $@"<DeviceInfo> <OutputFormat>EMF</OutputFormat> <PageWidth>{pageSettings.PaperSize.Width * 100}in</PageWidth> <PageHeight>{pageSettings.PaperSize.Height * 100}in</PageHeight> <MarginTop>{pageSettings.Margins.Top * 100}in</MarginTop> <MarginLeft>{pageSettings.Margins.Left * 100}in</MarginLeft> <MarginRight>{pageSettings.Margins.Right * 100}in</MarginRight> <MarginBottom>{pageSettings.Margins.Bottom * 100}in</MarginBottom> </DeviceInfo>"; Warning[] warnings; report.Render("Image", deviceInfo, (name, fileNameExtension, encoding, mimeType, willSeek) => { var stream = new MemoryStream(); allPageStreams.Add(stream); return stream; }, out warnings); } foreach (Stream stream in allPageStreams) stream.Position = 0; if (allPageStreams.Count == 0) throw new Exception("无待打印流数据"); // 初始化单个打印文档承载所有页面 var printDocument = new PrintDocument(); printDocument.DocumentName = "RDLC打印报表"; printDocument.PrintController = new StandardPrintController(); printDocument.DefaultPageSettings.Color = false; printDocument.DefaultPageSettings = pageSettings; printDocument.PrinterSettings.PrinterName = Properties.Settings.Default.PrinterName; DialogResult result = DialogResult.Retry; while (!printDocument.PrinterSettings.IsValid) { result = MessageBox.Show("错误,未选择默认打印机", "错误", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); if (result == DialogResult.Cancel) { Environment.Exit(0); } } var currentPageIndex = 0; printDocument.PrintPage += (sender, e) => { Metafile pageImage = new Metafile(allPageStreams[currentPageIndex]); Rectangle adjustedRect = new Rectangle( e.PageBounds.Left - (int)e.PageSettings.HardMarginX, e.PageBounds.Top - (int)e.PageSettings.HardMarginY, e.PageBounds.Width, e.PageBounds.Height); e.Graphics.FillRectangle(Brushes.White, adjustedRect); e.Graphics.DrawImage(pageImage, adjustedRect); currentPageIndex++; e.HasMorePages = (currentPageIndex < allPageStreams.Count); }; printDocument.EndPrint += (Sender, e) => { foreach (Stream stream in allPageStreams) stream.Close(); allPageStreams.Clear(); }; // 一次性提交所有页面的打印任务 printDocument.Print(); }
第二步:改造入口调用逻辑,增加打印机分支判断
替换main.cs中原有的循环打印逻辑,选中Microsoft Print to PDF时走批量合并打印,其他场景保留原有逻辑:
localReport = AddToLocalReport(localReport); localReport.ReportPath = Application.StartupPath + "\\RDLCreport.rdlc"; // 不区分大小写判断是否为微软PDF打印机 bool isMsPdfPrinter = Properties.Settings.Default.PrinterName.Equals("Microsoft Print to PDF", StringComparison.OrdinalIgnoreCase); if (isMsPdfPrinter) { var allReports = new List<LocalReport>(); for (int i = 1; i < printCounter + 1; i++) { // 按实际业务逻辑生成每一页的报表实例加入集合 var pageReport = AddToLocalReport(new LocalReport()); pageReport.ReportPath = Application.StartupPath + "\\RDLCreport.rdlc"; allReports.Add(pageReport); } if (Properties.Settings.Default.canPrint == true) { allReports.BatchPrintToPrinter(); } } else { // 其他打印机完全保留原有逐页打印逻辑 for (int i = 1; i < printCounter + 1; i++) { if (Properties.Settings.Default.canPrint == true) { localReport.PrintToPrinter(); } } }
注意事项
- 如果业务逻辑是给同一个LocalReport实例追加数据自动生成分页,不需要循环创建多个LocalReport,直接把单次渲染出的所有页面流通过
HasMorePages属性逐页输出即可,核心是仅调用一次printDocument.Print()提交任务 - 原代码中先读取报表横向配置又强制设置
pageSettings.Landscape = true的逻辑如果是调试遗留,正式环境可删除,避免和报表实际页面方向配置冲突 - 打印机名称判断使用不区分大小写的比对,避免系统语言、配置差异导致匹配失败
内容的提问来源于stack exchange,提问作者KamadoTanjiro2
相关产品推荐
相关产品推荐

