• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

ASP.NET之Web打印-终极解决篇

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
做过bs开发的同志应该都深有体会,在web程序中打印不再象应用程序中那样便于控制了,web程序天生的一些特性造成了这个缺点,如:打印机在本地,而文件确可能在服务器上;格式如何控制和定制等等。都给我们开发中带来了很多问题,虽说有水晶报表等控件来解决但总归是不方便。当然有了问题就会有人来研究解决,这里我先对目前流行的几种方式做个简单介绍

1、IE直接打印

        这个不用多说,直接调用window.print或者webrower控件的ExecWB方法来打印。方便快捷,客户端无需任何设置即可。利用一些办法也可以实现简单的定制,比如做一个模板htm文件,然后在js中动态创建一个隐藏帧来,用脚本来生成其中的数据,再把最后的结果文件写入到隐藏帧打印处理。如果处理的好,实际上效果也是不错。对于简单的打印需求应该是够了。这里我举个实际中的例子来说明这种方式:
         开发中经常需要打印一些统计的结果给用户,比如说一个常见的功能是营业报表类的打印:操作员先输入查询条件,然后提交得到查询的结果,点击打印后,按照定义好的格式打印报表。
   我们实现上大部分情况会把查询的结果绑定到DataGrid上来,然后打印DataGrid。这种情况的打印一般来说格式比较固定简单,确定后基本不会再作更改。所以可以采用IE直接打印,但若直接调用window.print来打印结果页面,页面上别的无关元素也会被打印出来,页头页尾的格式也不好控制,所以采用把需要打印的数据动态写入到隐藏帧中打印的方式来实现
代码示例:ASP.NET中打印指定的DataGrid内容
其中借用来自微软的一段js代码,整个js代码如下:

//以下脚本实现:打印指定的datagrid

var vGridContent;  //DataGrid的内容
var vHeaderInfo;  //打印的表头
var vTailerInfo;  //打印的表尾

/*
目的:在页面写入隐藏帧并打印
参数:
vDataGrid  所要打印的DataGrid句柄

备注:
  代码中调用如下
  btPrint.Attributes.Add( "onclick","return PrintDataGrid(document.all('SheetList'))");
 
  SheetList为待打印的DataGrid的ID
*/

function PrintDataGrid(vDataGrid)
{
  PickupHeaderInfo();
  
  document.body.insertAdjacentHTML( "beforeEnd",
    "<iframe name=printHiddenFrame width=0 height=0></iframe>");
 
  var doc = printHiddenFrame.document;
  doc.open();
  doc.write( "<body onload=\"setTimeout('parent.onprintHiddenFrame()', 0)\">");
  doc.write( "<iframe name=printMe width=0 height=0 ></iframe>");
  doc.write( "</body>");
  doc.close();
 
  CreateHtmlReport(printHiddenFrame.printMe,vDataGrid);
  return false;
    
}
     

/*
目的:在隐藏帧中写入DataGrid的内容,并重写DataGrid的格式
参数:
vHideFrame 隐藏帧的句柄
vDataGrid  所要打印的DataGrid句柄

备注:
*/
function CreateHtmlReport(vHideFrame,vDataGrid)
{
  vGridContent = vDataGrid.outerHTML;
 
  // 输出报表头信息及抽取过来的表格
  var doc = vHideFrame.document;
  doc.open();
  doc.write( "<html><body>");
  doc.write(vHeaderInfo);
  doc.write(vGridContent);
  doc.write( "</body></html>");
  doc.close();
 
 
  // 重新设置表格样式
  vDataGrid.borderColor = "#000000";
  vDataGrid.width = "100%";
  vDataGrid.style.fontFamily = "Verdana";
  vDataGrid.style.fontSize = "12px";
  vDataGrid.style.borderRight = "2px solid #000000";
  vDataGrid.style.borderTop = "2px solid #000000";
  vDataGrid.style.borderLeft = "2px solid #000000";
  vDataGrid.style.borderBottom = "2px solid #000000";
  vDataGrid.style.borderCollapse = "collapse";
  // 重新设置表格头样式
  var TBody = vDataGrid.children(0);
  TBody.children(0).style.fontWeight = "bold";
  TBody.children(0).bgColor = "#E7E7E7";
  // 替换原表格底部的页码信息
  var pageInfo = "<td>第 " + ((4 - 3) / 1 + 1) + " 页 / 共 " + "1" + " 页&nbsp;&nbsp;&nbsp;&nbsp;</td>";
}

//创建表头 表尾
function PickupHeaderInfo()
{
  try
  {
   // 提取报表标题字体大小
   var ReportTitleWithSizeInfo = "<font size='" + "+2" + "'>" + "无费用用户统计" + "</font>"
   var reportDate = "";
   var reportWriter = "";
   var nowdate=new Date();
   reportDate = "<b>统计时间</b>:" +nowdate.toLocaleString() + "<br>";
   reportDate += "<b>营业厅</b>:测试而已<br>";  
 
   // 生成报表头信息
   vHeaderInfo = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=gb2312\">";
   vHeaderInfo += "<title>无费用用户统计</title></head>" +
    "<body bgcolor='#FFFFFF' style='color: #000000; font-family: Verdana; font-size:12px; cursor: default'>";
   vHeaderInfo += "<br><p align='center'><b>" + ReportTitleWithSizeInfo + "</b></p>";
   vHeaderInfo += "<p>" + reportDate;
   vHeaderInfo += reportWriter + "</p>";
  }
  catch (e)
  {
   alert( "提取报表公共信息失败,打印操作被取消!");
   self.close();
  }
}

//下面的脚本来自msdn
// The code by Captain <[email protected]>
     // Mead & Company, http://www.meadroid.com/wpm/

// fake print() for IE4.x
if ( !printIsNativeSupport() )
  window.print = printFrame;

// main stuff
function printFrame(frame, onfinish) {
  if ( !frame ) frame = window;

  if ( frame.document.readyState !== "complete" &&
       !confirm("The document to print is not downloaded yet! Continue with printing?") )
  {
    if ( onfinish ) onfinish();
    return;
  }

  if ( printIsNativeSupport() ) {
    /* focus handling for this scope is IE5Beta workaround,
       should be gone with IE5 RTM.
    */
    var focused = document.activeElement;
    frame.focus();
    frame.self.print();
    if ( onfinish ) onfinish();
    if ( focused && !focused.disabled ) focused.focus();
    return;
  }

  var eventScope = printGetEventScope(frame);
  var focused = document.activeElement;

  window.printHelper = function() {
    execScript("on error resume next: printWB.ExecWB 6, 1", "VBScript");
    printFireEvent(frame, eventScope, "onafterprint");
    printWB.outerHTML = "";
    if ( onfinish ) onfinish();
    if ( focused && !focused.disabled ) focused.focus();
    window.printHelper = null;
  }

  document.body.insertAdjacentHTML("beforeEnd",
    "<object id=\"printWB\" width=0 height=0 \
    classid=\ "clsid:8856F961-340A-11D0-A96B-00C04FD705A2\"></object>");

  printFireEvent(frame, eventScope, "onbeforeprint");
  frame.focus();
  window.printHelper = printHelper;
  setTimeout( "window.printHelper()", 0);
}

// helpers
function printIsNativeSupport() {
  var agent = window.navigator.userAgent;
  var i = agent.indexOf( "MSIE ")+5;
  return parseInt(agent.substr(i)) >= 5 && agent.indexOf("5.0b1") < 0;
}

function printFireEvent(frame, obj, name) {
  var handler = obj[name];
  switch ( typeof(handler) ) {
    case "string": frame.execScript(handler); break;
    case "function": handler();
  }
}

function printGetEventScope(frame) {
  var frameset = frame.document.all.tags("FRAMESET");
  if ( frameset.length ) return frameset[0];
  return frame.document.body;
}

function onprintHiddenFrame() {
  function onfinish() {
    printHiddenFrame.outerHTML = "";
    if ( window.onprintcomplete ) window.onprintcomplete();
  }
  printFrame(printHiddenFrame.printMe, onfinish);
}
        
         程序中在Page_Load里面加上:btPrint.Attributes.Add( "onclick","return PrintDataGrid(document.all('SheetList'))");
         注:SheetList为需要打印的DataGrid ID,在查询后,btPrint为页面上打印按钮的ID
       
         可以将上述脚本代码写在一个js文件中,然后再aspx文件中引用,如 <script srcenter.js"></script>  ,上述代码的原理比较简单,我不在多说。上述代码可以实现直接打印页面上指定控件的内容,当然最多还是打印table的内容,如果需要先预览后打印。需要作一个空的html文件,然后动态写入需要打印的内容:
         var preDlg = window.open( "PrintList.htm");
         CreateHtmlReport(preDlg, true);
        

2、ActiveX控件

        自己开发控件。这种方式很多商用软件采用这种方式,写成控件后已经无所谓是在web中使用还是应用程序中使用了。打印方式非常灵活,基本上程序能做到的web也能做得到。但客户端需要安装组件,部署不是很方便。

3、.NET组件
        
         卢彦写过一篇很好的文章《利用XML实现通用WEB报表打印》,相信大家都看过了。思路新颖,实现简单,确实不失为一种通用WEB打印解决办法,尤其利用XML来描述打印文件的方法给以后的格式的拓展留下很好的接口,非常容易扩充。这种打印方式对于格式变化大,数据量小的应用来说非常合适。这种思路也给了ASP.NET上打印的一种新的思路:自定义一些组件来实现灵活的打印功能。当然缺点也是显而易见:1、需要客户端安装NET framework1.0组件。2、XML的解析上,如果文件较大速度上不是很理想。3、页面首次加载时会有明显的延时。当然最大的问题在于客户端需要安装组件,因为大部分采用BS架构的系统,客户端配置都不会太高,9x的操作系统居多,如果采用这种方式必将给工程的实施造成很多麻烦,所以最好能有一种方式:既能利用xml这种好的方式来描述打印文件,而且客户端也无需安装任何组件

        在研究了卢大侠的代码后,俺有了一个想法:事实上代码里别的功能我们并不关心,最重要的关键在于xml的解析部分和打印的部分。先来看看XmlDocument的命名空间System.Xml,并非winform特有,webform也可以使用,再看看PrintDocument的命名空间System.Drawing.Printing,查询了MSDN后发现这个命名空间下的类库webform中依然可以使用。好了,我们最关心的两点WebForm中都可以使用,我们可以把这个打印控件写成一个类库,然后在ASP.NET中直接调用而客户端无需再安装任何组件了。
         但随后问题出来了:卢彦的.NET组件是在页面请求的首次下载到客户端执行的,所以组件中可以直接使用各种本地资源,如打印机,网络等,但我们的打印控件写成类库由ASP.NET程序调用时,实际上组件是在服务端上运行,它访问服务端的资源不会有问题,但我们更希望:运行在服务端的组件可以访问客户端的资源,如访问客户端的打印机打印指定内容,当然打印的内容可能是在服务端生成的。
         这又引出一个新的问题:ASP.NET如何不受限制的访问各种资源。由于安全原因,ASP.NET程序默认以ASPNET 本地用户帐户运行。由于该帐户不具有任何网络凭据,因此在网络看来,它是 Windows 匿名帐户 (NT AUTHORITY\ANONYMOUS LOGON),不具有访问本地资源的权限,所以必须采用模拟用户的方式让APS.NET程序以别的帐户形式运行。
         思路已经整理清楚,简单说一些实现的步骤:

A、编写打印组件
         参考卢彦的代码,去除无关部分,只保留xml解析部分和打印部分,PrintControl类中增加三个成员数据:
         public string FileName= "";            //需要打印的文件名称
         public string PrinterName= "";        //打印机名称
         public string ClientIP= "";                //客户端IP地址
         增加一个成员函数:
         public void PrintPage()
   {
    try
    {
     doc.Load(FileName);
     // set the printer name
       this.printDocument1.PrinterSettings.PrinterName = ClientIP+PrinterName;
     // add print page event handler
     this.printDocument1.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
     // print the page
     //string tm=User.Identity.Name;
     this.printDocument1.Print();
     error_msg= "打印成功";
    }
    catch(Exception ex)
    {
     error_msg = ex.Message;
    }

  }
        
         注意:客户端的打印机必须是共享

         别的xml解析部分不用动,编译成类库后,在ASP.NET引用RemotePrint.dll,并在需要打印功能的页面放上一个打印按钮,代码中引用RemotePrint命名空间,编写Click事件如下:
    
        PrintControl print=new PrintControl();
        print.PrintPath =Request.PhysicalApplicationPath;
        print.ClientIP=Request.ServerVariables["REMOTE_ADDR"]     
        print.PrinterName="printer";
        print.PrintPage();

         ASP.NET项目中Web.Config开启用户模拟:
         <identity impersonate="true"  userName="1234" password="1234" />

         上述解决办法必须基于一个前提,服务端和客户端是运行在同一个网段内,不过仔细想想,有上述打印需求的BS系统一般都运行在企业的内网上,所以基本上满足要求


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
在ASP.NETMVC应用中使用NInject注入ASMX类型的WebService发布时间:2022-07-10
下一篇:
ASP.NET教程汇总发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap