ASP.NET之Web列印-終極解決篇

來源:互聯網
上載者:User
         做過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 <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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.