如何为PrintDialog.PrintDocument传递文件名?文件冲突时追加时间戳
FlowDocument导出PDF:指定文件名+自动处理占用冲突
解决思路
PrintDialog.PrintDocument本身无法直接传入文件名,因为它依赖系统打印对话框完成交互。要实现自动指定文件名、遇到文件被打开时自动添加时间戳,需要绕开对话框手动配置打印队列,同时添加异常捕获逻辑处理文件占用冲突。
完整代码实现
using System; using System.IO; using System.Linq; using System.Windows; using System.Windows.Documents; using System.Printing; public void ExportFlowDocToPdf(FlowDocument flowDoc, string baseFileName = "My Document") { // 获取系统内置的Microsoft Print to PDF虚拟打印机 var printQueue = GetPdfPrintQueue(); if (printQueue == null) { MessageBox.Show("未找到Microsoft Print to PDF打印机,请先启用该系统功能"); return; } // 配置FlowDocument的打印尺寸匹配打印机可打印区域 var printableArea = printQueue.GetPrintCapabilities().PageImageableArea; flowDoc.PageWidth = printableArea.ExtentWidth; flowDoc.PageHeight = printableArea.ExtentHeight; // 创建带页眉页脚的分页器(与原有代码逻辑一致) var paginator = new HeaderFooterPaginator<HeaderModel, FooterModel>(0, flowDoc, GetHeader(), GetFooter()); string outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{baseFileName}.pdf"); bool exportSuccess = false; while (!exportSuccess) { try { // 创建打印作业并指定输出文件路径 var printJob = printQueue.AddJob(baseFileName, paginator, false); if (printJob is PrintSystemJobInfo jobInfo) { jobInfo.Properties["OutputFile"] = outputPath; jobInfo.Commit(); } // 等待打印作业完成,避免提前结束导致文件损坏 while (!printJob.IsCompleted && !printJob.IsFailed && !printJob.IsCanceled) { System.Threading.Thread.Sleep(100); printJob.Refresh(); } if (printJob.IsCompleted) { exportSuccess = true; MessageBox.Show("PDF导出成功"); } else { throw new Exception("打印作业失败或被取消"); } } catch (IOException ex) when (ex.Message.Contains("被另一个进程使用")) { // 文件被占用时,自动生成带精确时间戳的新文件名 string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss"); outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{baseFileName}_{timestamp}.pdf"); MessageBox.Show($"文件正在被使用,将改用文件名:{Path.GetFileName(outputPath)}"); } catch (Exception ex) { MessageBox.Show($"导出失败:{ex.Message}"); break; } } } // 封装获取Microsoft Print to PDF打印队列的方法 private PrintQueue GetPdfPrintQueue() { var localPrintServer = new LocalPrintServer(); return localPrintServer.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections }) .FirstOrDefault(q => q.Name.Equals("Microsoft Print to PDF", StringComparison.OrdinalIgnoreCase)); }
关键点说明
- 绕开系统对话框:直接获取系统内置的PDF虚拟打印机,无需用户手动选择文件和打印机。
- 自动处理文件冲突:捕获文件被占用的
IOException,自动给文件名加上精确到秒的时间戳,避免重复文件名导致的崩溃。 - 确保导出完整性:循环检查打印作业状态,等待PDF完全生成后再结束流程,防止文件损坏。
更简洁的替代方案(第三方库)
如果觉得系统打印机配置繁琐,可以使用第三方库直接转换,比如PdfSharp,代码逻辑更简单:
using PdfSharp.Xps; using System.IO; using System.Windows.Xps.Packaging; public void ExportWithPdfSharp(FlowDocument flowDoc, string baseFileName) { string outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{baseFileName}.pdf"); try { // 先将FlowDocument转为XPS流,再转换为PDF using (var stream = new MemoryStream()) { var xpsDoc = new XpsDocument(stream, FileAccess.ReadWrite); var xpsWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc); xpsWriter.Write(((IDocumentPaginatorSource)flowDoc).DocumentPaginator); stream.Position = 0; XpsConverter.Convert(stream, outputPath, XpsConverterOptions.None); } MessageBox.Show("导出成功"); } catch (IOException ex) when (ex.Message.Contains("被另一个进程使用")) { // 生成带时间戳的新文件名并重试 string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss"); ExportWithPdfSharp(flowDoc, $"{baseFileName}_{timestamp}"); } catch (Exception ex) { MessageBox.Show($"导出失败:{ex.Message}"); } }
内容的提问来源于stack exchange,提问作者Harxish
相关产品推荐
相关产品推荐

