CODE-使用 C# 批次列印 PDF 檔案

專案遇到批次列印 PDF 檔需求。

Acrobat Reader 或 Foxit Reader 等常用 PDF 軟體本身就具備傳參數直接列印功能,例如 Acrobat Reader 直接列印 PDF 之語法為:AcroRd32.exe /p /h "pdf路徑" "印表機名稱"(印表機名稱省略時由預設印表機輸出)

基於以上資訊,最直覺的做法是找出 Acrobat Reader EXE 檔(AcroRd32.exe)路徑,在 .NET 程式透過 Process.Start() 傳入 PDF 路徑及 /p /h 參數呼叫 Acrobat Reader 列印檔案。但這個做法有個小缺點,它限制使用者必須安裝特定 PDF 閱讀軟體,再不然程式就得夠彈性,支援各種可列印 PDF 的軟體,如此尋找及識別 PDF 軟體邏輯將複雜化。

在 Stackoverflow 看到一個好方法,由於 Windows 多半會預設 PDF 開啟程式,並且還會註冊開啟、列印等動作,方便使用者透過檔案總管右鍵選單直接列印:

探索其背後原理,是 Acrobat Reader 先在 .pdf 副檔名註冊 UserChoice/ProgId = AcroExch.Document.11

而 AcroExch.Document.11 註冊了 Print/Command 對應到先前說過的列印指令: AcroRd32.exe /p /h "%1":

透過以上 Registry,當我們對 PDF 檔下達 Print Verb 時,Windows 便會找到對應程式並執行列印,不管它是 Acrobat Reader 還是 Foxit Reader,遠比指定並尋找特定軟體的做法更具彈性。以下為 Stackoverflow 找到的範例程式:

private void SendToPrinter()
{
   ProcessStartInfo info = new ProcessStartInfo();
   info.Verb = "print";
   info.FileName = @"c:\output.pdf";
   info.CreateNoWindow = true;
   info.WindowStyle = ProcessWindowStyle.Hidden;
 
   Process p = new Process();
   p.StartInfo = info;
   p.Start();
 
   p.WaitForInputIdle();
   System.Threading.Thread.Sleep(3000);
   if (false == p.CloseMainWindow())
      p.Kill();
}

仿照上述方法寫好第一版,丟給使用者測試後馬上被打槍-程式在列印多頁報表時會掉頁,例如 6 頁只印完 4 頁就沒了。

推敲其原因,由於 AcroRd32 非標準的命令列程式,無法等待程式執行結束,啟動程式後控制權即回到呼叫端,故範例程式的做法是等待三秒,假設文件已列印完畢即強制關閉 PDF 程式,造成 AcroRd32 6 頁只列了 4 頁就被關掉的狀況。(飄向北方才唱到咀嚼爆肚涮羊就被卡歌來著)

把 3 秒等待時間加長是種鋸箭做法,但魔術數字註定要糾結於「空等 vs 不足」的兩難。最後,我想出一個好方法-監測列印佇列(PrintQueue)。呼叫 AcroRd32 後先等待列印文件出現在 PrintQueue,再等待其列印完畢從佇列消失,最長等待時間則拉長到 180 秒,確保每個 PDF 都印好印滿,如此既沒有無謂等待,也沒有過早中止程式掉頁風險,新做法美妙到我想為自己起立鼓掌 XD(捻鬚而笑)

完整程式範例如下供大家參考:

 
 
//REF:https://stackoverflow.com/a/6106155/288936
public static void Print(string filePath)
{
    Status = PrintJobStatus.Printing;
    Message = string.Empty;
    try
    {
        logger.Debug($"Printing... {filePath}");
        ProcessStartInfo info = new ProcessStartInfo();
        info.Verb = "print";
        info.FileName = filePath;
        info.CreateNoWindow = true;
        info.WindowStyle = ProcessWindowStyle.Hidden;
 
        Process p = new Process();
        p.StartInfo = info;
        p.Start();
 
        p.WaitForInputIdle();
        //以下邏輯克服無法得知Acrobat Reader或Foxit Reader是否列印完成的問題
        //最多等待180秒(假設所有檔案可在3分鐘內印完)
        var timeOut = DateTime.Now.AddSeconds(180);
        bool printing = false; //是否開始列印
        bool done = false; //是否列印完成
                           //取純檔名部分,跟PrintQueue進行比對
        string pureFileName = Path.GetFileName(filePath);
        //限定最大等待時間
        while (DateTime.Now.CompareTo(timeOut) < 0)
        {
            if (!printing)
            {
                //未開始列印前發現檔名相同的列印工作
                if (CheckPrintQueue(pureFileName))
                {
                    printing = true;
                    Console.WriteLine($"[{pureFileName}]列印中...");
                }
            }
            else
            {
                //已開始列印後,同檔名列印工作消失表示列印完成
                if (!CheckPrintQueue(pureFileName))
                {
                    done = true;
                    Console.WriteLine($"[{pureFileName}]列印完成");
                    break;
                }
            }
            System.Threading.Thread.Sleep(100);
        }
        try
        {
            //若程序尚未關閉,強制關閉之
            if (false == p.CloseMainWindow())
                p.Kill();
        }
        catch
        {
        }
        if (!done)
        {
            Console.WriteLine($"無法確認報表[{pureFileName}]列印狀態!");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {DateTime.Now:HH:mm:ss} {ex.Message}");
    }
}
 
//需查詢 WMI 記得加入參照及 using System.Management; 
private static bool CheckPrintQueue(string file)
{
    //尋找PrintQueue有沒有檔案相同的列印工作
    string searchQuery =
        "SELECT * FROM Win32_PrintJob";
    var printJobs =
             new ManagementObjectSearcher(searchQuery).Get();
    return printJobs.Any(o => (string)o.Properties["Document"].Value == file);
}
歡迎推文分享:
Published 14 July 2017 11:59 PM 由 Jeffrey
Filed under: ,
Views: 6,656



意見

沒有意見

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<July 2017>
SunMonTueWedThuFriSat
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication