随心所欲的Web页面打印技术

随心所欲的Web页面打印技术

一.概述
  对基于B/S架构的应用程序而言,客户端的页面打印一直是比较头疼的问题,简单的做法是:1.使用IE的打印功能;2.使用水晶报表。但以上两种办法,都有很大的局限性,很难实现特殊要求的排版和精确的定位,所以不能满足一些特殊客户的BT要求。为此,本人总结了自己在使用Web打印上的一点经验,和大家分享。
  本文涉及以下技术:javascript、ActiveX、ASP.NET、GDI+。

二.基本架构
  首先,我们不能使用IE的打印功能,必须自己设计‘打印’按钮。很多人习惯将‘打印’按钮放在要打印的页面上,打印时为了不把这个按钮打印出来,采用办法如下:1.打印前隐藏按钮;2.打印;3.显示按钮。
我觉得这样比较麻烦,所以我采用框架。一共有三个页面:
1.main.htm :框架页面,上面是打印按钮,下面是要显示的页面。
2.header.htm :标题栏,至少包含一个打印按钮。
3.report.aspx :要打印的页面,由用户生成。

//main.htm
<html>
 <head>
  <title></title>  
 </head> 
  <frameset rows="10%,90%" frameborder="0" border="0" frameSpacing="0">
  <frame id="header" name="header" src="Header.htm" noresize scrolling="no">
  <frame id="report" name="report" src="Report.aspx" noresize scrolling="auto">
 </frameset> 
</html>

//header.htm
<html>
 <head>  
  <script id=clientEventHandlersJS language=javascript>
  <!--
  function btnPrint_onclick() 
  {   
     parent.report.focus();
     parent.report.print();  
  }
  //-->
  </script>
 </head>
 <body> 
  <INPUT id="btnPrint" type="button" value="Print" name="Print" οnclick="return btnPrint_onclick()">  
 </body>
</html>

  这样,在点击‘打印’按钮时,将直接打印report.aspx页面的内容,既简单又直观。

三.打印
 要完全控制打印,就必须由程序设定页眉、页脚、页边距。每个客户端的IE设置都不尽相同,你可以要求你的客户修改他们的打印设置为你指定的值,显然这不现实。所以正确的做法是:
1.备份客户打印页面设置;
2.设置页眉页脚上下左右边距为自己需要的值;
3.打印;
4.恢复原来的打印页面设置。

 这里用到一个叫ScriptX的控件。你需要下载一个文件:smsx.cab,下载地址:http://www.meadroid.com/scriptx/sxdownload.asp。这个地址并不能保证长期有效,你可以在搜索引擎上搜索‘ScriptX’以获得更多相关信息。

在“header.htm”中增加该控件的引用:
<OBJECT id="factory" style="DISPLAY: none" codeBase="http://localhost/WebApplication1/smsx.cab#VVersion=6,3,434,12"
 classid="clsid:1663ed61-23eb-11d2-b92f-008048fdd814" viewastext>
</OBJECT>
注意CodeBase指向你的实际存放文件的位置,在客户端第一次浏览该页面时,将下载并安装该控件,请确定客户端的安全设置允许安装控件。
那么此时 header.htm 的内容如下。

//header.htm
<html>
<head>
 <script id="clientEventHandlersJS" language="javascript">
 <!--
 function btnPrint_onclick() 
 {
  //备份客户打印机设置
   var h = factory.printing.header;
   var f = factory.printing.footer;
   var t = factory.printing.topMargin;
   var b = factory.printing.bottomMargin;
   var l = factory.printing.leftMargin;
   var r = factory.printing.rightMargin;
 
   //设置页眉页脚上下左右边距  
   factory.printing.header = "";
    factory.printing.footer = "";
    factory.printing.topMargin="0";
   factory.printing.bottomMargin="0";
   factory.printing.leftMargin="0";
   factory.printing.rightMargin="0";
  
   //打印
   parent.report.focus();
   parent.report.print()
  
   //恢复原来的打印设置
   factory.printing.header = h;
   factory.printing.footer = f;
    factory.printing.topMargin=t;
   factory.printing.bottomMargin=b;
   factory.printing.leftMargin=l;
   factory.printing.rightMargin=r; 
 }
 //-->
 </script>
 
 <OBJECT id="factory" style="DISPLAY: none" codeBase="http://localhost/WebApplication1/smsx.cab#VVersion=6,3,434,12"
  classid="clsid:1663ed61-23eb-11d2-b92f-008048fdd814" viewastext>
 </OBJECT>
</head>
<body bgColor="#9999cc">
 <INPUT id="btnPrint" type="button" value="Print" name="Print" οnclick="return btnPrint_onclick()">   
</body>
</html>

  此时,不管客户端IE设置如何,都能正确打印页面,打印内容将完全取决于页面:report.aspx。

四.动态页面生成
1. 如果你的页面是静态页面,或者页面元素为固定数量,那就非常简单了。只要调整好各个元素位置就行了,绝对所见即所得。要注意的是一个A4纸的大小为21cm×29.7cm,对应象素大约为 794×1123 (系统分辨率96DPI) 。

2.如果你要生成一些图表,可以使用GDI+,你自己绘制的图片绝对能满足客户需求。下面给一个例子,我在页面放一个Label,一个Image。Image动态生成,调整其位置使其打印时出现在第二页的左上角。

 1 public   class  Report : System.Web.UI.Page
 2   {
 3  protected System.Web.UI.WebControls.Image Image1;
 4  protected System.Web.UI.WebControls.Label Label1;
 5 
 6  Web 窗体设计器生成的代码
17
18  private void Page_Load(object sender, System.EventArgs e)
19  {   
20   if(!Page.IsPostBack)
21   {
22    InitImage();
23   }

24  }

25
26  private void InitImage()
27  {
28   Bitmap bmp = new Bitmap(800,1120);
29   Graphics g =  Graphics.FromImage(bmp);
30
31   g.FillRectangle(Brushes.Gray,0,0,800,1120);
32
33   g.FillRectangle(Brushes.RoyalBlue,0,0,100,600);
34   g.FillRectangle(Brushes.Aqua,600,0,100,600);
35   g.FillRectangle(Brushes.Coral,700,0,100,600); 
36 
37   g.FillRectangle(Brushes.YellowGreen,0,800,800,100);
38   g.FillRectangle(Brushes.Beige,0,900,800,100);
39   g.FillRectangle(Brushes.SkyBlue,0,1000,800,100);
40   g.FillRectangle(Brushes.Tomato,0,1100,800,20);
41
42   string filename = Server.MapPath("TempImages\\img1.jpg");   
43
44   bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Jpeg);
45
46   this.Label1.Text = filename;
47   this.Image1.ImageUrl = filename;
48   this.Image1.Attributes["style"]="POSITION: absolute; LEFT: 0cm;  TOP: 29.7cm"//定位
49  }
  
50 }

51
52

注意:要保证存放图片的目录,有写权限。

3.以上技术只适合于页面元素为固定数量的情况,对于页面内容大小不定的情况,例如,要打印一份人员信息的清单,人员数量为1~1000不等,每页显示20条记录,要有规定的页眉、页脚,此时该如何处理。
  思路:ASP.NET页面都有一个基类System.Web.UI.Page,该类有一个保护方法叫void Render(HtmlTextWriter writer),就是通过这个方法,ASP.NET在后台把WEB服务器端控件的属性转换成HTML代码,并发送到客户端供浏览器显示。我们的办法就是“劫持”该方法,完全手工生成所需页面标记代码。
先看一个例子:
 public class FrmRYInfo : System.Web.UI.Page
 {
  private void Page_Load(object sender, System.EventArgs e)
  {   
  }
  protected override void Render(HtmlTextWriter writer)
  {
   writer.Write("<HTML>");
   writer.Write("<body>"); 
   writer.Write("<h1>Hello,world!</h1>");  
   writer.Write("</body>");
   writer.Write("</html>");
  }
 }
  编译并运行以上程序,可以看到一行一号字体的"Hello,world!",查看页面源文件,内容如下:
 <HTML><body><h1>Hello,world!</h1></body></html>
  对照上面代码,应该非常好理解,下面我们就做一个实际的例子,将信息从数据库读出,显示在页面上,每页显示15条记录。

  protected   override   void  Render(HtmlTextWriter writer)
  
{
   writer.Write(
"<HTML>");
   writer.Write(
"<body>");  

   DataTable tabRY 
= GetCustomerInfo(); //读取数据库

   
int Lines = 15;    //每页行数
   int Count = tabRY.Rows.Count;
   
int TotalPage = Count/Lines + (Count%Lines==0?0:1);

   
for(int CurrentPage =0; CurrentPage<TotalPage; CurrentPage++)
   
{
    
int StartRow = CurrentPage * Lines;
    
int EndRow = StartRow + Lines;
    
if(EndRow > Count) EndRow = Count;

    ProcessCurrentPage(writer,tabRY,StartRow,EndRow,CurrentPage,TotalPage);
   }

  
   writer.Write(
"</body>");
   writer.Write(
"</html>");
  }


  
private   void  ProcessCurrentPage(HtmlTextWriter writer, DataTable tabRY,  int  StartRow,  int  EndRow,  int  

CurrentPage, 
int  TotalPage)
  
{
   
if(CurrentPage != 0)
   
{
    writer.Write(
"<p  style=page-break-before:always></p>");
              }

   
   writer.Write(
"<table  width=630 height=417 border=0>");
   writer.Write(
" <tr>");
   writer.Write(
"  <td width=624 height=47><div align=center  style=font-size:24px>人员信息汇总

</div></td>");
   writer.Write("  </tr>");
   writer.Write(
"  <tr>");
   writer.Write(
"  <td height=222>");

   writer.Write(
"   <table width=623  border=1 cellpadding=0 cellspacing=0>");
   writer.Write(
"   <tr>");
   writer.Write(
"    <td width=134><div align=center>姓名</div></td>");
   writer.Write(
"    <td width=134><div align=center>编号</div></td>");
   writer.Write(
"    <td width=134><div align=center>电话</div></td>");
   writer.Write(
"    <td width=134><div align=center>小灵通</div></td>");         
   writer.Write(
"   </tr>");

  
for(int i=StartRow; i<EndRow; i++)
  

   DataRow row 
= tabRY.Rows[i];
   
string XM = row["MC"].ToString();
   
string BH = row["BH"].ToString();
   
string DH = row["LXDH"].ToString();if(DH.Length==0)DH="-";
   
string XLT =  row["XLT"].ToString();

   writer.Write(
"   <tr>");
   writer.Write(
"    <td width=134><div align=center>" + XM + "</div></td>");
   writer.Write(
"    <td width=134><div align=center>" + BH + "</div></td>");
   writer.Write(
"    <td width=134><div align=center>" + DH + "</div></td>");
   writer.Write(
"    <td width=134><div align=center>" + XLT + "</div></td>");     

    
   writer.Write(
"   </tr>");
  }


   writer.Write(
"   </table>");

   writer.Write(
"  </td>");
   writer.Write(
"  </tr>");
   writer.Write(
"  <tr>");
   writer.Write(
"  <td height=37><div align=right>第" + (CurrentPage+1).ToString() +"页,共" + 

TotalPage.ToString() 
+ "页</div></td>");
   writer.Write(
"  </tr>");
   writer.Write(
"</table>");
  }


感觉又回到了用记事本做网页的年代,手工生成HTML代码,是不是真正叫“随心所欲”。
几点说明:
(1)在每一页(除了第一页)的头部加入 writer.Write("<p  style=page-break-before:always></p>"); 目的是控制在打印时,打印机在此换页。这里通过强制打印机换页来实现页面的布局,与上面的绝对定位的办法不同。该标记只影响打印,不影响显示。
(2)用记事本做网页绝对很痛苦,而且HTML标记也很不好用,我的办法是:用Dreamweaver生成需要的页面,再参照其HTML代码进行编程。
(3)尽量使用HtmlTextWriter类提供的一些其它方法如WriteBeginTag等取代Write方法,这样可以提高页面在客户端的兼容性。同时在每个标记后加入writer.WriteLine();进行换行,以便于调试。

控件功能强大,却简单易用,所有调用如同JavaScript扩展语句, 主要接口函数如下: PRINT_INIT 打印初始化 SET_PRINT_PAGESIZE 设定纸张大小 ADD_PRINT_HTM 增加超文本项 ADD_PRINT_TEXT 增加纯文本项 ADD_PRINT_TABLE 增加表格项 ADD_PRINT_SHAPE 画图形 SET_PRINT_STYLE 设置对象风格 PREVIEW 打印预览 PRINT 直接打印 PRINT_SETUP 打印维护 PRINT_DESIGN 打印设计 ... 样例清单 1.如何在页面内嵌入控件见样例一 2.如何选材打印当前页面内容见样例二 3.如何用代码生成打印页见样例三 4.如何打印设计和定位套打见样例四 5.如何控制纸张大小和连续打印见样例五 6.如何输出多页长文档及双面打印见样例六 7.如何定向输出见样例七 8.如何打印图片见样例八 9.如何用程序加载打印维护背景图见样例九 10.如何控制打印样式(STYLE)见样例十 11.如何直接打印条形码见样例十一 12.如何读写本地文件见样例十二 13.如何打印旋转内容见样例十三 14.如何按URL打印见样例十四 15.如何打印表格的页头页尾见样例十五 16.如何设置预览窗口大小见样例十六 17.如何发打印机指令或直接读写端口见样例十七 18.如何打印幅面高度不固定的票据见样例十八 19.如何内嵌显示及预览时包含背景图见样例十九 20.如何强制分页并预览多页卡片见样例二十 21.如何控制打印维护的功能权限见样例二十一 22.如何构建自己的纯WEB打印预览见样例二十二 23.如何居中打印超文本见样例二十三 24.如何选择界面皮肤见样例二十四 25.如何指定输出到哪页或仅预览见样例二十五 26.如何提高多页打印的性能见样例二十六 27.如何导出数据到Excel文件见样例二十七 28.如何快速读取客户端系统信息见样例二十八 29.如何使用其它长度单位见样例二十九 30.如何分页输出页面内容见样例三十 31.如何打印表格的分页小计或合计见样例三十一 32.如何实现清晰的图表打印见样例三十二 33.如何实现甘特图等的图表打印见样例三十三 34.如何使用百分比%和满页打印见样例三十四 35.如何获得打印结果和程序代码见样例三十五 36.如何在设计过程中用js编辑内容见样例三十六 37.如何打印公章效果图见样例三十七 38.如何用BASE64编码输出图片见样例三十八 39.如何打印田字格、上划线等文本见样例三十九 40.如何进行数据格式转换见样例四十 41.如何把内容关联后按顺序打印见样例四十一 42.如何把整页内容缩放打印见样例四十二 43.如何分页打印综合表格见样例四十三 44.如何缩放打印单个超文本内容见样例四十四 45.如何获得打印状态及最终结果见样例四十五 46.如何设置右边距和下边距见样例四十六
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值