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. css

    1.css代码语法 p{font-size:12px;color:red;}p 为选择符:又称选择器,指明网页中要应用样式规则的元素,如本例中是网页中所有的段(p)的文字将变成蓝色,而其他的元素(如o ...

  2. 讲座:Influence maximization on big social graph

    Influence maximization on big social graph Fanju PPT链接: social influence booming of online social ne ...

  3. Mongodb的安装使用

    1.下载 最好不要去.com的那个网站下载:    各个版本的下载地址: http://dl.mongodb.org/dl/win32/x86_64 2.压缩包版本: 下载压缩包版本,目录结构如图: ...

  4. C#工程引用自定义目录的DLL

    在App.config里配置: <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-mi ...

  5. tomcat启动报错: org.apache.catalina.deploy.WebXml addFilter

    解决方法为:在Tomacat7的context.xml文件里的<Context>中加上<Loader delegate="true" />

  6. C/C++ 中的include

    当需要使用已有的方法或库时, 可以将它们的头文件#include进来. #include会在preprocess过程中被替换成它包含的代码. 头文件中包含了需要使用的函数/变量的声明. 当然声明与定义 ...

  7. asp.net mvc 权限过滤和单点登录(禁止重复登录)

    1.权限控制使用controller和 action来实现,权限方式有很多种,最近开发项目使用控制控制器方式实现代码如下 /// <summary> /// 用户权限控制 /// < ...

  8. hibernate缓存机制(转)

    原文出处:http://www.cnblogs.com/wean/archive/2012/05/16/2502724.html 一.why(为什么要用Hibernate缓存?) Hibernate是 ...

  9. Leetcode 45. Jump Game II

    Given an array of non-negative integers, you are initially positioned at the first index of the arra ...

  10. cocos2d-x-2.2.6 project-creator文件夹下python.bat

    @echo off set /p projectName=请输入项目名称: if "%projectName%"=="" goto inputError set ...