C#打印PDF文档并实时监测打印进度

C# 专栏收录该内容
4 篇文章 0 订阅

公司承接了一个项目,要做集中打印服务和后期处理流程,现在正在结构分析和技术验证。

项目需求是:客户端通过OA系统上传打印请求到服务器,服务器提交PDF文档打印,打印完成后做后期处理。。。项目的后期处理部分要做什么不是本文涉及的内容,这里仅涉及打印。

首先是系统选型,选择范围是android、linux和windows:

  1.  android,似乎默认不支持打印机,搞起来很麻烦,放弃;
  2.  linux,有打印机驱动,开发语言可选java或C#,不过linux开发没涉及过,估计坑儿不少,搁置;
  3.  windows,有打印机驱动,开发语言用C#,系统支持PrintService,似乎还可以,就是它了。

好久没做C#开发了,语法都忘了,有点郁闷。

先上网搜索了很多关于C#打印的文章和代码,初步确定了开发方案:

  1. 开发一个SDK,就是一个C#类库;
  2. 使用Spire.Pdf载入Pdf文件,设置打印参数,并启动打印,Spire.Pdf的官网下载:https://www.e-iceblue.com/Download/download-pdf-for-net-now.html
  3. 使用Windows打印服务监测打印进度。

第一步,启动打印

导入Spire.Pdf,设置参数,开始打印。所有步骤都很顺利,测试一次通过。以下是核心代码:

        public class PrintOptions
        {
            /// <summary>
            /// 指定打印机,缺省使用系统默认打印机
            /// </summary>
            public string PrinterName { get; set; }

            /// <summary>
            /// 指定纸类型,缺省使用打印机默认纸类型
            /// </summary>
            public string PaperName { get; set; } 

            /// <summary>
            /// 是否双面打印,缺省单面打印
            /// </summary>
            public bool Duplex { get; set; }

            /// <summary>
            /// 是否横向打印,缺省竖向打印
            /// </summary>
            public bool Landscape { get; set; }

            /// <summary>
            /// 回调程序异常
            /// </summary>
            public Action<Exception> OnException;

            /// <summary>
            /// 回调打印错误,当回调参数id&lt;0时意思是打印任务被放弃
            /// </summary>
            public Action<int, PrintJobStatus> OnError;

            /// <summary>
            /// 回调处理进度和打印进度,具体是哪种进度取决于最后一次<see cref="OnChanged"/>的status参数
            /// </summary>
            public Action<int, int> OnProgress;

            /// <summary>
            /// 回调状态改变,正在处理,正在打印,打印完成,无纸,错误。。。
            /// </summary>
            public Action<int, PrintJobStatus> OnChanged;
        }

        private void PrintInternal(string pdfFilename, PrintOptions options)
        {
            using (var doc = new PdfDocument())
            {
                doc.LoadFromFile(pdfFilename);

                // 不弹出打印对话框
                doc.PrintSettings.PrintController = new StandardPrintController();

                if (!string.IsNullOrEmpty(options.PrinterName))
                {
                    doc.PrintSettings.PrinterName = options.PrinterName;
                }
                if (!string.IsNullOrEmpty(options.PaperName))
                {
                    var ps = GetPaperSize(options.PaperName);
                    if (ps != null) doc.PrintSettings.PaperSize = ps;
                }
                if (options.Duplex && doc.PrintSettings.CanDuplex)
                {
                    doc.PrintSettings.Duplex = Duplex.Horizontal;
                }
                if (options.Landscape)
                {
                    doc.PrintSettings.Landscape = options.Landscape;
                }
                doc.Print();
            }
        }

        private PaperSize GetPaperSize(string paperName)
        {
            using (var doc = new PrintDocument())
            {
                foreach (PaperSize ps in doc.PrinterSettings.PaperSizes)
                {
                    if (ps.PaperName.Equals(paperName, StringComparison.OrdinalIgnoreCase)) return ps;
                }
            }
            return null;
        }

代码说明:

这段代码的核心是PrintInternal,它实现的功能无非就是调用Spire.PDF,设置参数,开始打印。

备注:PrintOptions的OnException,OnError,OnProgress和OnChanged是回调接口,我不喜欢delegate+event的方法定义回调(要写太多代码),还是Action简单。

第二步,监测打印进度。

这才遇到了麻烦。

一开始我选择了System.Printing.PrintQueue来做,心想这毕竟是人家官方实现或包装的完善方案。然而,不幸的事还是发生了(或者不能说不幸仅仅是一点点小麻烦)。

第一个麻烦是,Spire.Pdf的打印似乎是同步的,在启动Print()方法后,我在Windows系统的"设备与打印机"里的打印任务监测窗口中能立刻看到打印任务,但是直到打印完成Print()方法才完成,我的程序才能继续执行。就是说直到打印完成我才能继续检测打印进度,这可能是由于我测试代码使用的是虚拟打印机,打印速度跟打印队列处理速度一样快。我的解决办法是:在开始打印前,启动一个子线程并在其中检测打印进度。

第二个麻烦是,Spire.Pdf启动的打印任务,在System.Printing.PrintQueue里生成的PrintSystemJobInfo对象,启动的大部分方法和属性,都要求在与Print()方法运行在同一个线程了,否则(至少80%可能,还不是100%)会报异常:调用线程无法访问此对象,因为另一个线程拥有该对象。我的解决办法是:使用WMI代替PrintQueue和PrintSystemJobInfo。
    
首先通过万能的百度,查询到System.Printing.PrintQueue和PrintSystemJobInfo貌似是Win32_PrintJob的包装,所以我就用WMI模仿并实现了PrintSystemJobInfo的大部分功能,这样整个开发过程就能重新回到MSDN的轨道上来。(对于我这样的C#程序员,MSDN及其延伸资料还是非常样有用的)

以下是我用WMI实现的代替PrintSystemJobInfo的数据对象:

public class PrintJobInfo
        {
            public string Document { get; set; }

            public uint JobId { get; set; }

            public string JobStatus { get; set; } // e.g. 正在后台打印 | 正在打印

            public uint PagesPrinted { get; set; }

            public string PaperSize { get; set; } // e.g. A4 210 x 297 mm

            public uint Priority { get; set; } // e.g.1

            public uint TotalPages { get; set; }

            public string TimeSubmitted { get; set; }

            public string Status { get; set; }

            public uint StatusMask { get; set; }

            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsInError"/>
            /// </summary>
            public bool IsInError { get { return (StatusMask & (int)PrintJobStatus.Error) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsPrinted"/>
            /// </summary>
            public bool IsPrinted { get { return (StatusMask & (int)PrintJobStatus.Printed) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsPaused"/>
            /// </summary>
            public bool IsPaused { get { return (StatusMask & (int)PrintJobStatus.Paused) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsDeleting"/>
            /// </summary>
            public bool IsDeleting { get { return (StatusMask & (int)PrintJobStatus.Deleting) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsCompleted"/>
            /// </summary>
            public bool IsCompleted { get { return (StatusMask & (int)PrintJobStatus.Completed) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsRestarted"/>
            /// </summary>
            public bool IsRestarted { get { return (StatusMask & (int)PrintJobStatus.Restarted) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsSpooling"/>
            /// </summary>
            public bool IsSpooling { get { return (StatusMask & (int)PrintJobStatus.Spooling) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsPrinting"/>
            /// </summary>
            public bool IsPrinting { get { return (StatusMask & (int)PrintJobStatus.Printing) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsRetained"/>
            /// </summary>
            public bool IsRetained { get { return (StatusMask & (int)PrintJobStatus.Retained) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsBlocked"/>
            /// </summary>
            public bool IsBlocked { get { return (StatusMask & (int)PrintJobStatus.Blocked) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsDeleted"/>
            /// </summary>
            public bool IsDeleted { get { return (StatusMask & (int)PrintJobStatus.Deleted) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsPaperOut"/>
            /// </summary>
            public bool IsPaperOut { get { return (StatusMask & (int)PrintJobStatus.PaperOut) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsOffline"/>
            /// </summary>
            public bool IsOffline { get { return (StatusMask & (int)PrintJobStatus.Offline) > 0; } }
            /// <summary>
            /// <seealso cref="PrintSystemJobInfo.IsUserInterventionRequired"/>
            /// </summary>
            public bool IsUserInterventionRequired { get { return (StatusMask & (int)PrintJobStatus.UserIntervention) > 0; } }

            public void Cancel()
            {
                var query = "SELECT * FROM Win32_PrintJob WHERE JobId=" + JobId;
                using (var searcher = new ManagementObjectSearcher(query))
                {
                    var enumerator = searcher.Get().GetEnumerator();
                    if (enumerator.MoveNext())
                    {
                        ((ManagementObject)enumerator.Current).Delete();
                    }
                }
            }


            public void Refresh()
            {
                var newJob = Get((int)JobId);
                if (newJob != null)
                {
                    JobStatus = newJob.JobStatus;
                    TotalPages = newJob.TotalPages;
                    Status = newJob.Status;
                    StatusMask = newJob.StatusMask;
                } else
                {
                    JobStatus = "";
                    TotalPages = 0;
                    Status = "";
                    StatusMask = 0;
                }
            }

            public override string ToString()
            {
                return $"JobId: {JobId}, Document:{Document}, Status:{Status}, JobStatus:{JobStatus}, Pages: {TotalPages}";
            }

            /// <summary>
            /// 获取特定的任务信息
            /// </summary>
            /// <param name="jobId"></param>
            /// <returns></returns>
            public static PrintJobInfo Get(int jobId)
            {
                var query = "SELECT * FROM Win32_PrintJob WHERE JobId=" + jobId;
                using (var searcher = new ManagementObjectSearcher(query))
                {
                    var collection = searcher.Get();
                    if (collection.Count > 0)
                    {
                        foreach (ManagementObject item in collection)
                        {
                            return Parse(item);
                        }
                    }
                }
                return null;
            }

            /// <summary>
            /// 获取全部任务信息
            /// </summary>
            /// <returns></returns>
            public static IEnumerable<PrintJobInfo> GetAll()
            {
                var result = new List<PrintJobInfo>();
                var query = "SELECT * FROM Win32_PrintJob";
                using (var searcher = new ManagementObjectSearcher(query))
                {
                    var collection = searcher.Get();
                    if (collection.Count > 0)
                    {
                        foreach (ManagementObject item in collection)
                        {
                            result.Add(Parse(item));
                        }
                    }
                }
                return result;
            }

            /// <summary>
            /// 获取最新的任务信息,新任务的JobId &gt; lastJobId <br/>
            /// 如果lastJobId为null,那么取第一个任务信息
            /// </summary>
            /// <param name="lastJobId"></param>
            /// <returns></returns>
            public static PrintJobInfo GetNext(uint? lastJobId)
            {
                var query = "SELECT * FROM Win32_PrintJob";
                if (lastJobId != null) query += " WHERE JobId > " + lastJobId;
                using (var searcher = new ManagementObjectSearcher(query))
                {
                    var collection = searcher.Get();
                    if (collection.Count > 0)
                    {
                        foreach (ManagementObject item in collection)
                        {
                            return Parse(item);
                        }
                    }
                }
                return null;
            }

            private static PrintJobInfo Parse(ManagementObject data)
            {
                var result = new PrintJobInfo
                {
                    Document = (string)data.Properties["Document"].Value,
                    JobId = (uint)data.Properties["JobId"].Value,
                    JobStatus = (string)data.Properties["JobStatus"].Value,
                    PagesPrinted = (uint)data.Properties["PagesPrinted"].Value,
                    PaperSize = (string)data.Properties["PaperSize"].Value,
                    Priority = (uint)data.Properties["Priority"].Value,
                    TotalPages = (uint)data.Properties["TotalPages"].Value,
                    TimeSubmitted = (string)data.Properties["TimeSubmitted"].Value,
                    Status = (string)data.Properties["Status"].Value,
                    StatusMask = (uint)data.Properties["StatusMask"].Value
                };
                return result;
            }
        }

代码说明:

这个PrintJobInfo对象的成员只要分为5部分:

  1. WMI成员部分,命名同WMI中的属性名称完全相同,仅保留了经测试有效果的属性;
  2. 模仿PrintSystemJobInfo中有关打印状态的全部属性;
  3. 打印过程干涉方法,仅实现了Refresh和Cancel,其他的比如Pause、Resume、Restart等方法在我的项目里没用,所以就不麻烦了。
  4. 打印任务查询方法,这跟PrintSystemJobInfo不一样,这里实现了 Get、GetAll、GetNext三个方法来从打印队列里搜索打印任务。
  5. 剩下的Parse方法就是解析WMI数据为PrintJobInfo对象,ToString方法仅用于调试。

我使用虚拟打印机Microsoft Print to PDF测试,能正确并及时的监测到Windows打印池的任务进度。

我的测试代码:

            PDFPrinter printer = new PDFPrinter();
            var fileName = "D:\\test.pdf";
            printer.Print(fileName, new PDFPrinter.PrintOptions()
            {
                PrinterName = "Microsoft Print to PDF",
                OnChanged = (id, status) =>
                  {
                      Console.WriteLine($"id:{id}, status: {status}");
                  },
                OnError = (id, status) =>
                {
                    Console.WriteLine($"id:{id}, status: {status}");
                },
                OnProgress = (id, pageNumber) =>
                {
                    Console.WriteLine($"id:{id}, pageNumber: {pageNumber}");
                },
                OnException = (ex) =>
                  {
                      Console.WriteLine($"Exception: {ex.Message}");
                  }
            });

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();

输出结果:

id:50, status: Spooling
id:50, pageNumber: 1
id:50, pageNumber: 2
id:50, pageNumber: 3
id:50, pageNumber: 4
id:50, pageNumber: 5
id:50, pageNumber: 6
id:50, pageNumber: 7
id:50, pageNumber: 8
id:50, pageNumber: 9
Press any key to exit...
id:50, status: Printing
id:50, status: Completed

在测试中只能见到Spooling、Printing和Completed这三个状态,并能实时得到Spooling的进度,这个进度数值同Windows的设备与打印机里的打印任务监视窗口中完全一致。

以上是全部的内容。文中包含了全部的代码,就不另外添加附件了。

  • 1
    点赞
  • 8
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

首先机子上安装有office,在COM中添加引用Microsoft.Word.11.0.Object.Library(或11.0以上) Microsoft.Office.Interop.Word.Application myWordApp = null; Microsoft.Office.Interop.Word.Document doc = null; object Filename = path + "\\" + TaskID + ".docx";//目的文件 object templateFile = System.Windows.Forms.Application.StartupPath + @"\Template.docx";//模板文件,有一个五列一行的表 System.IO.File.Copy(templateFile.ToString(), Filename.ToString(), true);//模板WORD中有一个五列的表头,分别是卡号,串口号,发送指令条数,接收指令条数,收发成功率 myWordApp = new Microsoft.Office.Interop.Word.Application(); doc = myWordApp.Documents.Open(ref Filename, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing, ref Nothing); /////显示页码 object oAlignment = Microsoft.Office.Interop.Word.WdPageNumberAlignment.wdAlignPageNumberCenter; object oFirstPage = true; oAlignment = Microsoft.Office.Interop.Word.WdPageNumberAlignment.wdAlignPageNumberCenter; myWordApp.Selection.Sections[1].Footers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].PageNumbers.Add(ref oAlignment, ref oFirstPage); myWordApp.Selection.Sections[1].Footers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].PageNumbers.NumberStyle = Microsoft.Office.Interop.Word.WdPageNumberStyle.wdPageNumberStyleNumberInDash; for (int i = 2; i < 102; i++)//举例100台 { doc.Tables[1].Rows.Add(ref Nothing);//表格增加一行 doc.Tables[1].Cell(i, 1).Range.Text = "250297";//卡号 doc.Tables[1].Cell(i, 2).Range.Text = "COM12";//串口号 doc.Tables[1].Cell(i, 3).Range.Text = "100";//发送指令条数 doc.Tables[1].Cell(i, 4).Range.Text = "99";//接收指令条数
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值