專案裡要求將一個HTML頁面(支付結果)產生pdf文件。頁面有圖片,有表格,貌似開源的iTextSharp應付不了.
在一番搜尋之後,找到了wkhtmltopdf,一個命令列的開源轉換工具,支援指定url或本地html file的路徑,試用後效果不錯,還特意用wkhtmltopdf寫了一個工具將博客園的帖子備份pdf到本地,後續有空把這個工具分享出來
But,發給客戶測試兩天運行效果不太理想,出現一些未知錯誤,而且奇怪的是測試環境沒問題,但正式環境頻繁出錯。最後客戶放棄這個方案
附上WkhtmlToXSharp C# wrapper wrapper (using P/Invoke) for the excelent Html to PDF conversion library wkhtmltopdf library.
***
OK,來到正題,另類的解決方案:Hook
新建WinForm 項目,拖入WebBrowser控件,程式碼指定Url到本機html檔案路徑,等待文件載入完成後WebBrowser.Print(); OK,運行,會彈出選擇印表機的對話框,如圖一。點選列印後,彈出另存為的對話框,輸入xps路徑後儲存(圖二),即可得到一份xps文件。
圖一:選擇印表機
#圖二:輸入xps路徑
從上面可以看到,這裡的列印需要與UI交互,人工點擊列印,輸入xps路徑保存才行。
接下來在網路搜尋:怎麼不顯示對話框,直接列印產生xps文件,在stackoverflow,codeproject看了很多,沒找到辦法。後來偶然翻到園子前人的文章,採用hook方式,UI Automation來完成打印和保存的動作,覺得這個方案可行
接下來上代碼吧
//调用WebBrowser.Print的代码就忽略了,直接看钩子 IntPtr hwndDialog; string pathFile; EnumBrowserFileSaveType saveType; // Imports of the User32 DLL. [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetDlgItem(IntPtr hWnd, int nIDDlgItem); [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern private bool SetWindowText(IntPtr hWnd, string lpString); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsWindowVisible(IntPtr hWnd); //Win32 Api定义 [DllImport("user32.dll")] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfeter, string lpszClass, string lpszWindow); [DllImport("user32.dll")] static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, String lParam); [DllImport("user32.dll")] static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); //Win32消息定义 const uint WM_SETTEXT = 0x000c; const uint WM_IME_KEYDOWN = 0x0290; const uint WM_LBUTTONDOWN = 0x0201; const uint WM_LBUTTONUP = 0x0202; // The thread procedure performs the message loop and place the data public void ThreadProc() { int maxRetry = 10; int retry = 0; IntPtr hWndPrint = FindWindow("#32770", "打印"); IntPtr hWnd = FindWindow("#32770", "文件另存为"); if (hWnd != IntPtr.Zero) { log.InfoFormat("got saveas dialog handle. Printer Dialog skipped."); } else { Thread.Sleep(200); hWndPrint = FindWindow("#32770", "打印"); //这里有时候获取不到window,所以加了Sleep,多试几次 while (hWndPrint == IntPtr.Zero && retry < maxRetry) { Thread.Sleep(200); log.InfoFormat("retry get Print dialog handle.retry:{0}", retry); hWndPrint = FindWindow("#32770", "打印"); retry++; } if (hWndPrint == IntPtr.Zero) { //wait 1 second,retry again Thread.Sleep(1000); hWndPrint = FindWindow("#32770", "打印"); } if (hWndPrint == IntPtr.Zero) { log.InfoFormat("Did not get Print dialog handle.retry:{0}", retry); return; } log.InfoFormat("got Print dialog handle.retry:{0}", retry); //select printer dialog IntPtr hChildP; hChildP = IntPtr.Zero; hChildP = FindWindowEx(hWndPrint, IntPtr.Zero, "Button", "打印(&P)"); // 向保存按钮发送2个消息,以模拟click消息,借此来按下保存按钮 PostMessage(hChildP, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero); PostMessage(hChildP, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero); Application.DoEvents(); } //hWnd = FindWindow("#32770", null); hWnd = FindWindow("#32770", "文件另存为"); //To avoid race condition, we are forcing this thread to wait until Saveas dialog is displayed. retry = 0; while ((!IsWindowVisible(hWnd) || hWnd == IntPtr.Zero) && retry < maxRetry) { Thread.Sleep(200); log.InfoFormat("retry get saveas dialog handle.retry:{0}", retry); hWnd = FindWindow("#32770", null); retry++; Application.DoEvents(); } log.InfoFormat("got saveas dialog handle.retry:{0}", retry); if (hWnd == IntPtr.Zero) { //wait 1 second,retry again Thread.Sleep(1000); hWnd = FindWindow("#32770", "文件另存为"); } if (hWnd == IntPtr.Zero) { return; } Application.DoEvents(); IntPtr hChild; // 由于输入框被多个控件嵌套,因此需要一级一级的往控件内找到输入框 hChild = FindWindowEx(hWnd, IntPtr.Zero, "DUIViewWndClassName", String.Empty); hChild = FindWindowEx(hChild, IntPtr.Zero, "DirectUIHWND", String.Empty); hChild = FindWindowEx(hChild, IntPtr.Zero, "FloatNotifySink", String.Empty); hChild = FindWindowEx(hChild, IntPtr.Zero, "ComboBox", String.Empty); hChild = FindWindowEx(hChild, IntPtr.Zero, "Edit", String.Empty); // File name edit control // 向输入框发送消息,填充目标xps文件名 SendMessage(hChild, WM_SETTEXT, IntPtr.Zero, pathFile); // 等待1秒钟 System.Threading.Thread.Sleep(1000); // 找到对话框内的保存按钮 hChild = IntPtr.Zero; hChild = FindWindowEx(hWnd, IntPtr.Zero, "Button", "保存(&S)"); // 向保存按钮发送2个消息,以模拟click消息,借此来按下保存按钮 PostMessage(hChild, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero); PostMessage(hChild, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero); // Clean up GUI - we have clicked save button. //GC is going to do that cleanup job, so we are OK Application.DoEvents(); //Terminate the thread. return; }
接下來有關xps轉pdf,使用了Spire.Pdf,官方有demo,這裡不再說明
有圖有真相
以上是另類解決Html to Pdf 的方案詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!