發現一篇不錯的打印文章:
http://www.cnblogs.com/seabluescn/archive/2006/11/23/569945.html
隨心所欲的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動態生成,調整其位置使其打印時出現在第二頁的左上角。
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條記錄。
{
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();進行換行,以便於調試。
遲些在把我項目中涉及的自定義格式EXCEL輸出的也寫上來補充下。