做過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" + " 頁 </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 <cerebrum@iname.com> // 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系統一般都運行在企業的內網上,所以基本上滿足要求 From:http://hi.baidu.com/zyjweb/blog/item/014458342c8edf4f251f1486.html |