You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 23:51:21