Background

项目里要求将一个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

调用IE打印功能,使用XPS打印机,先将HTML文件生成xps文档,再生成pdf

新建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,这里不再说明

有图有真相

有关自动选择XPS Document Writer的hook代码我还没完成,各位赐教!

Html to Pdf 的另类解决方案的更多相关文章

  1. asp.net2.0导出pdf文件完美解决方案【转载】

    asp.net2.0导出pdf文件完美解决方案 作者:清清月儿 PDF简介:PDF(Portable Document Format)文件格式是Adobe公司开发的电子文件格式.这种文件格式与操作系统 ...

  2. Windows7 IIS7.5 HTTP Error 503 The service is unavailable 另类解决方案

    这篇文章是在你看了别的解决方案仍然解决不了之后才有用. 所以再未用别的解决方案之前,用了该解决方案依然无效的话,请自己看着办. 原创: .net2.0和.net3.5的应用程序池请在开始菜单打开VS2 ...

  3. MacOS 的预览 Preview 打开pdf 容易卡死 解决方案

    MacOs 10.13.6 打开pdf之后容易卡死. 移动一下窗口之后就卡死了. 有时候等一会还能缓过来,有时候就缓不过来了. 只要执行下这个命令就可以了. sudo rm -rf ~/Library ...

  4. mssql sql server上如何建一个只读视图–视图锁定的另类解决方案

    转自:http://www.maomao365.com/?p=4508 <span style="color:red;font-weight:bold;">我们熟知一个 ...

  5. js 回调地狱的另类解决方案尝试

    例如 通过学生获取学生所在学校信息,需要先查询学生所在班级,再通过班级查询所在学校信息.js代码类似写法如下: function getStudentSchool(id) { ajax.get(&qu ...

  6. Delphi存取图像完整解决方案

    http://blog.sina.com.cn/s/blog_693cf1cf0100plkq.html 对于涉及图像数据的数据库应用程序,图像数据的存取技术是一个关键.由于缺少技术文档及DEMO例程 ...

  7. R: 导入 csv 文件,导出到csv文件,;绘图后导出为图片、pdf等

    ################################################### 问题:导入 csv 文件 如何从csv文件中导入数据,?参数怎么设置?常用参数模板是啥? 解决方 ...

  8. JavaScript - 如果...没有方法

    这篇文章源于我上一周所读的一篇12年的文章.原作者提出了一个问题,如果js没有原生方法Math.round(),我们如何去实现呢? 对此我和我的基友进行了小小探讨,并给出了一些有意思的答案. 本文内容 ...

  9. [知识库分享系列] 二、.NET(ASP.NET)

    最近时间又有了新的想法,当我用新的眼光在整理一些很老的知识库时,发现很多东西都已经过时,或者是很基础很零碎的知识点.如果分享出去大家不看倒好,更担心的是会误人子弟,但为了保证此系列的完整,还是选择分享 ...

随机推荐

  1. [LeetCode] Reverse Integer 翻转整数

    Reverse digits of an integer. Example1: x = 123, return 321 Example2: x = -123, return -321 click to ...

  2. JavaScript OOP 之「创建对象」

    工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题. function createPers ...

  3. Android开发之import org.apache.http

    在build.gradle(app)中添加一下依赖: android { useLibrary 'org.apache.http.legacy' } dependencies { compile 'o ...

  4. oracle日常——数据库备份

    1.进入cmd 2.运行命令 exp [scott]/[orcl]@[orcl] file=[d:\oracle_back\scott_orcl.dmp] owner=scott 格式如下: exp ...

  5. iOS推送流程

    1. 在apple开发者帐号上创建一个BundleID,创建证书或者Xcode上都是用这个BundleID(例如com.mycompany.pushDemo) 2. 代码层面: 在capability ...

  6. 调戏OpenShift:一个免费能干的云平台

    一.前因后果 以前为了搞微信的公众号,在新浪sae那里申请了一个服务器,一开始还挺好的 ,有免费的云豆送,但是一直运行应用也要消费云豆,搞得云豆也所剩无几了.作为一名屌丝,日常吃土,就单纯想玩一玩微信 ...

  7. WPF学习系列 绘制旋转的立方体

    我是一年经验的web程序员,想学习一下wpf,比较喜欢做项目来学习,所以在网上找了一些项目,分析代码,尽量能够做到自己重新敲出来 第一个项目是 中间的方块会不停的旋转. 第一步,新建wpf项目 第二步 ...

  8. Steamroller

    FCC题目:对嵌套的数组进行扁平化处理.你必须考虑到不同层级的嵌套. 示例: steamroller([[["a"]], [["b"]]]) 应该返回 [&qu ...

  9. oracle--逻辑对象--bai

    1 序列 sequence oracle特有.实现"自增"或"自减"的逻辑对象. 2 同义词 synonym 对表取别名,该别名被永久存储. 比视图更省资源. ...

  10. 移动web页面前端开发总结

    移动web在当今的发展速度是一日千里,作为移动领域的门外汉,在这段时间的接触后,发现前端开发这一块做一个小小的总结. 1.四大浏览器内核 1.Trident (IE浏览器) :因为在早期IE占有大量的 ...