如何使用WebBrowser控件打印格式化的XML文档,并以编程方式任意设置打印方向(C#完整示例)

我用C#开发的一个WinForm软件中,需要连续打印一批用XSLT格式化的XML文档。一般来说,这种打印操作用WebBrowser控件的Print()函数是最简单的方法(除了采用现成的收费的打印控件,有可能是唯一的方法)。
不幸的是,我要打印的这批文档,有的需要纵向打印,有的需要横向打印,交错着输出。

要知道,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++方法了。设置打印方向的方式就是:先设置全局钩子,然后打开【页面设置】窗口
      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,作者也是我,所以不是抄袭:)
posted @ 2010-12-10 11:57  cs_liwei  阅读(3467)  评论(2编辑  收藏  举报