如何使用WebBrowser控件打印格式化的XML文档,并以编程方式任意设置打印方向(C#完整示例)
我用C#开发的一个WinForm软件中,需要连续打印一批用XSLT格式化的XML文档。一般来说,这种打印操作用WebBrowser控件的Print()函数是最简单的方法(除了采用现成的收费的打印控件,有可能是唯一的方法)。
不幸的是,我要打印的这批文档,有的需要纵向打印,有的需要横向打印,交错着输出。
要知道,WebBrowser控件虽然提供了函数ShowPageSetupDialog()用来显示【页面设置】画面,却不提供用编程方式设置打印方向的接口。作为一个浏览器控件,出于安全性的考虑有一些功能的局限还是可以理解的,但是连续的打印,总不能打完一张就弹一次【页面设置】窗口,让用户点击一下再继续吧。在网上搜索一下,询问WebBrowser如何实现横向打印的人比比皆是,但似乎还没有人提出过一个比较完整的解决方案。
不幸的是,我要打印的这批文档,有的需要纵向打印,有的需要横向打印,交错着输出。
要知道,WebBrowser控件虽然提供了函数ShowPageSetupDialog()用来显示【页面设置】画面,却不提供用编程方式设置打印方向的接口。作为一个浏览器控件,出于安全性的考虑有一些功能的局限还是可以理解的,但是连续的打印,总不能打完一张就弹一次【页面设置】窗口,让用户点击一下再继续吧。在网上搜索一下,询问WebBrowser如何实现横向打印的人比比皆是,但似乎还没有人提出过一个比较完整的解决方案。
由于WebBrowser本身的局限性不可逾越,解决问题的思路其实比较明朗:打开【页面设置】画面后,用编码方式模拟用户的鼠标操作选择打印方向,然后模拟点击OK按钮关闭画面,让打印继续。刨去很多实现细节不说,这个思路完全可行,但当我测试的时候发现---连续打印的时候,屏幕上一闪一闪不停的打开关闭【页面设置】煞是晃眼---我现在用的是C#代码来捕捉【页面设置】画面,当我能捕捉到它时候,实际上它已经显示在用户面前了,即使程序一瞬间就将它关闭,在屏幕上闪烁一下却不可避免。
那么我要解决的是:在【页面设置】画面显示之前捕捉到并且关闭【页面设置】画面--纠结啊。
那么我要解决的是:在【页面设置】画面显示之前捕捉到并且关闭【页面设置】画面--纠结啊。
能做到这件事的就是CBT Hook。(作为一个.Net的开发者,可能对它不太熟悉,系统钩子是什么解释了一些基本概念,[Hook]c#中应用系统级全局钩子(CBT)这篇博文讲述了具体的利用方法)。简而言之,通过CBTHook我们可以在画面显示之前拦截到【画面将要显示】这个讯息,拦截到之后马上进行一系列的模拟操作,然后立刻关闭这个画面,结果这个画面基本上做到了还没有显示就被关闭。
全局钩子的操作没有办法在C#的托管代码中实现,所以我参照上面的文章,用C++在VS2008中创建了一个很简单的dll,里面只有三个函数:
1,SetCBTHook() -- 设置全局钩子
2,UnSetCBTHook() -- 用完之后取消掉全局钩子
3,CBTHookCallback() -- 监视系统中是否有画面要打开,如果发现一个新画面,发送一个自定义的消息"PRINTLIB_HOOK_HCBT_ACTIVATE" 给设置这个全局钩子的线程(也就是我的C#程序)
在C#代码中,使用DllImport之后就可以调用上面的C++方法了。设置打印方向的方式就是:先设置全局钩子,然后打开【页面设置】窗口
1,SetCBTHook() -- 设置全局钩子
2,UnSetCBTHook() -- 用完之后取消掉全局钩子
3,CBTHookCallback() -- 监视系统中是否有画面要打开,如果发现一个新画面,发送一个自定义的消息"PRINTLIB_HOOK_HCBT_ACTIVATE" 给设置这个全局钩子的线程(也就是我的C#程序)
在C#代码中,使用DllImport之后就可以调用上面的C++方法了。设置打印方向的方式就是:先设置全局钩子,然后打开【页面设置】窗口
private void SetPrintOrient(EOrientationType orientationType) { menumOrientationType = orientationType; try { SetCBTHook(this.Handle, GetCurrentThreadId());//设置钩子 wbPrint.ShowPageSetupDialog();//打开页面设置窗口 } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }
【页面设置】窗口要打开的时候,我们刚才设置的钩子马上给C#这边报信,C#这边在WndProc() 函数中捕获到这个"PRINTLIB_HOOK_HCBT_ACTIVATE"信息,马上进行一系列处理:
protected override void WndProc(ref Message m) { if (m.Msg == RegisterWindowMessage("PRINTLIB_HOOK_HCBT_ACTIVATE")) { IntPtr hwndPageSetting = m.WParam; StringBuilder sbtitle = new StringBuilder(256); GetWindowText(hwndPageSetting, sbtitle, 256); if (sbtitle.ToString() == PAGE_SETUP_TITLE) { //设置横向纵向(具体参照示例代码) ... //设置完了关闭页面设置窗口 SendMessage(hwndPageSetting, WM_CLOSE, IntPtr.Zero, null); //摘掉我们刚才设置的钩子 UnSetCBTHook(); } } base.WndProc(ref m); }
代码是一个完整Solution,基于Visual Studio 2008 SP1开发,可以直接打开,并运行一个WinForm示例程序。
Form1.cs 中演示了如何使用这种方法,用【纵-横-纵-横】的方向连续打印四个XML文档
private void button1_Click(object sender, EventArgs e) { XmlPrintLib.XmlPrinter.Print(this, Path.Combine(Application.StartupPath, "XMLFile1.xml"), false); XmlPrintLib.XmlPrinter.Print(this, Path.Combine(Application.StartupPath, "XMLFile2.xml"), true); XmlPrintLib.XmlPrinter.Print(this, Path.Combine(Application.StartupPath, "XMLFile1.xml"), false); XmlPrintLib.XmlPrinter.Print(this, Path.Combine(Application.StartupPath, "XMLFile2.xml"), true); }
如果有必要你可以直接将示例中的内容拷贝到你的工程中,随意使用(当然可能需要一些改动)。
除了本文提到的内容,实际的打印处理要考虑的细节很多,这些都包括在代码当中了。
要注意的地方:
根据操作系统以及IE版本的不同,打开的【页面设置】窗口的布局不尽相同,控件显示的名称不一样。而我是根据控件的名称来寻找鼠标模拟操作的目标的,所以在App.Config文件中我设置了一些参数来存放控件的名称。手动修改App.Config,可以分别用于三国语言的操作系统。
如果需要更智能一点的处理方式,只能请自行修改代码了。
App.Config文件是这个样子滴:
<appSettings> <!-- English --> <!-- <add key="PAGE_SETUP_TITLE" value="Page Setup" /> <add key="BUTTON_PORTRAIT" value="Portrait" /> <add key="BUTTON_LANDSCAPE" value="Landscape" /> <add key="BUTTON_OK" value="OK" /> --> <!-- Japanese --> <!-- <add key="PAGE_SETUP_TITLE" value="ページ設定" /> <add key="BUTTON_PORTRAIT" value="縦" /> <add key="BUTTON_LANDSCAPE" value="横" /> <add key="BUTTON_OK" value="OK" /> --> <!-- Chinese --> <add key="PAGE_SETUP_TITLE" value="页面设置" /> <add key="BUTTON_PORTRAIT" value="纵向" /> <add key="BUTTON_LANDSCAPE" value="横向" /> <add key="BUTTON_OK" value="确定" /> </appSettings>
补充说明:
1,也可以从SkyDrive下载代码2,本文的英文版张贴在codeproject,作者也是我,所以不是抄袭:)
--------------
路漫漫其修远兮
路漫漫其修远兮