Fastreport生成WEB報表


 開發WEB應用系統通常都會遇到報表打印問題。簡單應用可利用IE的頁面打印功能,利用HTML標簽控制格式來實現。但復雜的業務型應用系統,報表不僅是組成應用的 重要部分,還常常是相當復雜的。現在很多應用系統都要求提供自定義報表的功能——即客戶可以自行設計、修改報表。

    在C/S結構系統中,報表問題有很多成熟的解決方法。如DELPHI開發工具不僅自帶有報表控件,還可以利用第三方控件來實現快速靈活的報表制作和打印,其中有名的控件是FR-Software & A.Tzyganenko 的FastReport。FastReport提供了能與DELPHI無縫集成的從設計到打印的完整控件包,提供的設計界面友好靈活,對於開發可讓用戶自定義報表的C/S應用來說,是一種很好的解決方式。

    在B/S結構應用中,Crystal Report是一種大型報表系統常用和推薦的解決方案。但Crystal Report目前價格昂貴,而且該系統相當龐大。它的可定制性及精確控制打印效果方面尚不夠完善。當然,在目前市場上,它仍是一種首選的WEB應用的報表解決方案。

    如果能將C/S應用中成熟的報表解決方案搬到B/S應用中,相信對於大部分開發人員來說,都是非常歡迎的。本文將講述一個在JAVA環境中利用FastReport實現B/S應用中用戶可自定義的報表解決方案。因為筆者近段時間正用DELPHI、JAVA做一些項目,所以樣例代碼就以DELPHI、JAVA編寫。

    本解決方案樣例的基本環境是: WINDOWS 2000 SERVER+SQL SERVER 2000+TOMCAT 4.0。開發工具:IntelliJ IDEA 3.0,DELPHI 5.0。客戶端為IE 5.0瀏覽器。

    方案共要求用DELPHI編寫兩個程序,一個是將被包含在網頁中並在瀏覽器中運行的ACTIVEX(.ocx),一個是運行在服務器端的報表處理程序,中間通過JAVA程序連接——或任何其他WEB語言都可以,如ASP、PHP等。方案的基本原理圖如下:

    報表設計過程
    
    報表打印過程
    
    REPORT SERVER:可以做成一個普通的WINDOWS程序,也可以做成一個COM程序(Automation Object)。在報表設計過程中,用戶端(ACTIVEX)向WEB SERVER發送報表設計請求,請求中包含要設計報表的名稱;WEB SERVER 收到該請求后,調用REPORT SERVER請求設計的報表文件;REPORT SERVER收到請求后,先裝載報表的數據環境,然后將報表設計文件(.frf)和該報表的數據環境文件壓縮成一個包文件(.zip),將該包文件的完整路徑名返回給WEB SERVER調用程序;WEB SERVER將包文件回送給用戶端(ACTIVEX),用戶端將接收到的包文件保存到本地硬盤上,並解壓縮,從數據環境文件中恢復數據環境,通過FASTREPORT的相應控件打開報表文件給用戶提供可視化設計。用戶在ACTIVEX中設計報表時,雖然不能和數據庫連接,但因數據環境已存在,所以用戶仿如在通常的C/S應用結構下設計報表,能正常地看到報表的數據字典信息。在報表打印過程中,用戶端(ACTIVEX)向WEB SERVER發送報表打印/預覽請求,請求中包含要打印/預覽的報表名稱;WEB SERVER 收到該請求后,調用REPORT SERVER請求打印或預覽的報表文件;REPORT SERVER收到請求后,先裝載報表的數據環境,然后裝載報表文件(.frf),接着在無界面狀態下運行報表,最后將生成的已准備的報表文件(.frp)壓縮成一個包文件(.zip),將該包文件的完整路徑名返回給WEB SERVER調用程序;WEB SERVER將包文件回送給用戶端(ACTIVEX),用戶端將接收到的包文件保存到本地硬盤上,並解壓縮,通過FASTREPORT的相應控件打開報表文件(.frp)給用戶預覽或打印或重新調整格式或輸出為其他格式文件。用戶在ACTIVEX中預覽報表,仿如在通常的C/S應用結構下預覽報表。

    WEB SERVER:提供通常的WEB服務功能。

    ACTIVEX:ActiveX是Microsoft提出的一組使用COM(Component Object Model,部件對象模型)使得軟件部件可在網絡環境中進行交互的技術集。它與具體的編程語言無關。作為針對Internet應用開發的技術,ActiveX被廣泛應用於WEB服務器以及客戶端的各個方面。本方案中的ACTIVEX控件主要做兩方面的事情,一是利用FASTREPORT控件進行報表處理,包括報表設計(.frf文件)和報表打印(.frp文件)。一是與WEB SERVER進行通信,請求和接收包文件。本文樣例的ACTIVEX采用DELPHI 5.0編寫。

    下面分述各部分的一例具體實現(因為僅為說明方案的實現,所以很多代碼細節都進行了簡省)。

    一、             REPORT SERVER

    REPORT SERVER既可以做成一個普通的WINDOWS程序,也可以做成一個COM程序(Automation Object)。本例中為簡化見,采用普通的WINDOWS程序實現。

    在DELPHI中NEW一個應用程序。在FORM中加入TfrReport、TfrDBDataSet、ADOConnection、TADOQuery等控件——為了使用FASTREPORT的控件,需要安裝該控件包,可從站點http://www.fast-report.com下載,國內很多軟件站點都提供該控件包的下載。其中TfrDBDataSet、TADOQuery控件視應用需要可加入多個,另外為了壓縮文件,還要加入一個壓縮控件,本例使用VCLZip。在Form1中加入三個函數:preDesignReport(rpFileName:String),prePrintReport(rpFileName:String),zipReportFiles(rpFileName:String),分別用於准備報表設計文件、准備報表打印文件、壓縮報表文件 。Form1.Create方法為:

procedure TForm1.FormCreate(Sender: TObject);
var
    rpFileName,mode:String;
begin
  if paramCount>1 then
    begin
        mode:=paramStr(1);
      rpFileName:=paramStr(2);
      if mode='d' then   //設計報表
      if preDesignReport(rpFileName) then
          zipReportFiles(rpFileName);
      if mode='r' then   //打印報表
        if prePrintReport(rpFileName) then
            zipReportFiles(rpFileName);
   end;
   Application.Terminate;
end;


    程序根據調用參數判斷是准備報表設計文件還是准備報表打印文件,接着調用相應的過程來實現。最后的Application.Terminate 是讓程序執行功能后即退出——因為這是服務端程序,是不能與用戶交互的。
  preDesignReport(rpFileName:String)方法  

function TForm1.preDesignReport(rpFileName:String):boolean;
var

……   //其他變量
        dtfFileName:String;
begin
    ……
    dtfFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.dtf', [rfReplaceAll, rfIgnoreCase]);
  try
        rpAdoquery.SQL.Add('…');
        rpAdoquery.open;//打開報表的數據環境
        rpAdoquery.FieldList.SaveToFile(dtfFileName);
        result:=true;
   except
        on Exception do
            result:=false;
   end;
end;


    函數preDesignReport的作用是准備報表設計文件。報表中可以引用多個DataSet,本例假設報表只引用一個名為rpAdoquery的DataSet。rpFileName 為報表文件名(.frf),DtfFileName為保存數據環境的文件名(.dtf)。因為用戶端不能連接數據庫,所以將DataSet中的Fileds通過rpAdoquery.FieldList.SaveToFile(dtfFileName)保存到文件,和報表文件一起傳送給用戶端的ACTIVEX,ACTIVEX利用.dtf文件復現報表的數據環境。
    prePrintReport(rpFileName:String)方法:
    

function TForm1.prePrintReport(rpFileName:String):boolean;
var
……
    repFileName:String;
begin
        ……
   repFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.frp', [rfReplaceAll, rfIgnoreCase]);
   try
       rpAdoquery.SQL.Add('…');
       rpAdoquery.open;//打開報表的數據環境
       frReport1.ShowProgress:=False;
       frReport1.Clear;
       frReport1.LoadFromFile(rpFileName);
       frDBDataSet1.DataSet :=rpAdoquery;
       frReport1.Dataset :=frDBDataSet1;
       frReport1.PrepareReport;
       frReport1.SavePreparedReport(repFileName);
       result:=true;
   except
       on Exception do
       result:=false;
    end;
end;


    函數prePrintReport的作用是准備打印的報表文件,即先在服務器端裝載報表並運行,將運行好的報表保存為文件,用於傳送到用戶端進行預覽或打印。RepFileName是已准備好的報表文件名(.frp)。同樣假設報表只引用一個名為rpAdoquery的DataSet。frReport1.ShowProgress:=False 使報表運行過程中不顯示進度窗口(服務器端不能顯示與用戶交互的界面);接下來frReport1.Clear;…裝載報表文件及設置相關數據屬性;frReport1.PrepareReport 是在不顯示預覽窗口的情況下運行報表;frReport1.SavePreparedReport(repFileName) 將運行好的報表保存到文件,該文件傳送給用戶端的ACTIVEX,ACTIVEX可以直接預覽或顯示該報表。
    zipReportFiles(rpFileName:String)方法:

function TForm1.zipReportFiles(rpFileName:String):boolean;
var
  ……
  zipFileName,fileName:String;
  zipCount:Integer;
begin
  ……
  zipFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.zip',[rfReplaceAll, rfIgnoreCase]);
  fileName:= ExtractFileName(rpFileName);
  fileName:= ChangeFileExt(fileName,'.*');
  try
    VCLZip1.ZipName:= zipFileName;
    VCLZip1.RootDir:= '."';
    VCLZip1.FilesList.Add(fileName);
    zipCount:= VCLZip1.Zip;
    if zipCount = 0 then
      result:=false
    else
      result:=true;
  except
    on Exception do
      result:=false;
  end;
end;


    函數zipReportFiles的作用是把要傳送給用戶端的報表文件壓縮為一個.zip文件,簡化文件傳送過程,而且壓縮了數據量。ACTIVEX接收到.zip文件后,先解壓出包中文件,再進行處理。
二、             WEB SERVER 
    方案中WEB SERVER的作用主要是根據ACTIVEX的請求調用REPORT SERVER,並將REPORT SERVER生成的.zip文件發送給ACTIVEX。樣例通過一個report.jsp文件來處理:ACTIVEX通過get請求report.jsp文件,report.jsp文件調用REPORT SERVER處理后,將.zip文件發送給ACTIVEX。
    Report.jsp文件:

<%@ page import="…"%>
<%@page contentType=" APPLICATION/OCTET-STREAM" %>
<%
  try{
    String reqFileName = request.getParameter("rpFileName");
    String reqMode = request.getParameter("mode");//d為設計報表,r為打印報表
    String rpFileName = xxxx.getRpFileName(reqFileName); //根據請求的報表名獲得實際的報表文件名,如請求訂單報表,而訂單報表實際對應的報表文件為order.frf。
    String l_cmd="reportserver.exe "+reqMode+" "+ reqFileName;
    Process l_ps=java.lang.Runtime.getRuntime().exec(l_cmd,null);
    byte[] l_b=new byte[100];
    while(l_ps.getInputStream().read(l_b,0,100)!=-1){
      ..;
    }

    //發送文件
    String zipFileName = xxxx.getZipFileName(reqFileName); //獲得壓縮文件名
    response.setHeader("Content-Disposition","attachment; filename=""" + zipFileName + """");
    java.io.FileInputStream fileInputStream = new java.io.FileInputStream(zipFileName);

    int i;
    while ((i=fileInputStream.read()) != -1) {
     out.write(i);
    }
    fileInputStream.close();
    out.close();
  }catch(Exception e){
    ……
  }
%>

    String l_cmd="reportserver.exe "+reqMode+" "+ reqFileName; 組成調用REPORT SERVER的命令串。while(l_ps.getInputStream().read(l_b,0,100)!=-1){ ; } 等待REPORT SERVER執行完成,否則,程序在啟動REPORT SERVER后即執行下一行語句。發送文件的方式有多種,比如也可以由ACTIVEX通過ftp方式取得。

三、ACTIVEX

    方案中的ACTIVEX控件主要做兩方面的事情,一是報表利用FASTREPORT控件進行報表處理,包括報表設計(.frf文件 )和報表打印(.frp文件)。一是與WEB SERVER進行通信,請求和接收包文件。

    在DELPHI中NEW一個ActiveForm 應用,取名為reportAForm。在form中加入Combox、button、edit、label等與用戶交互的控件;為了處理報表,加入FASTREPORT的多個frSpeedButton用於處理報表事件,如設計、預覽、打印、翻頁、保存等;加入frReport、frDBDataSet、frDesigner等用於在運行時設計報表;如果設計報表時要使用圖形、復選框等內容,也要加入相應的控件;加入frPreview、frTextExport、frRTFExport等控件使可以預覽報表並可以將報表輸出為text、rtf等格式文件;加入ADOQuery(根據實際需要可加入多個)為報表設計提供數據環境,ADOQuery不OPEN,不與數據庫連接;加入NMHTTP用於與WEB SERVER聯系。加入四個函數:DesignReport(rpFileName:String),PrintReport(rpFileName:String),unzipReportFiles(rpFileName:String),getReportFile(rpFileName,mode:String)分別用於設計報表、打印報表、解壓縮報表和向WEB SERVER發送請求以取得報表文件 。
    getReportFile(rpFileName,mode:String)方法:

function TreportAForm.getReportFile(rpFileName,mode:String):boolean;
var
  ……
  zipFileName:String;
begin
  ……
  zipFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.zip',[rfReplaceAll, rfIgnoreCase]);
  try
    NMHTTP1.inputFileMode := TRUE;
    NMHTTP1.body:='." '+ zipFileName;
    NMHTTP1.Get('http://www…./../report.jsp?rpFileName='+rpFileName+'&mode='+mode);
    Result:=true;
  except
  on Exception do
    Result:=false;
  end;
end;

    函數getReportFile的作用是向WEB SERVER發送報表請求(通過NMHTTP的Get方法),並將返回的壓縮包文件保存到本地硬盤(zipFileName)。
    unzipReportFiles(rpFileName:String)方法:

function TreportAForm.unzipReportFiles(rpFileName:String) :boolean;
var
  ……
  zipFileName,fileName:String;
  zipCount:Integer;
begin
  ……
  zipFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.zip',[rfReplaceAll, rfIgnoreCase]);
  fileName:= ExtractFileName(rpFileName);
  fileName:= ChangeFileExt(fileName,'.*');
  try
    VCLUnZip1.ZipName:= '."'+ zipFileName;
    VCLUnZip1.DestDir:= '."';
    VCLUnZip1.OverwriteMode:= Always;
    VCLUnZip1.ReadZip;
    VCLUnZip1.FilesList.Add(fileName);
    zipCount:= VCLUnZip1.UnZip;
    if zipCount = 0 then
      result:=false
    else
      result:=true;
  except
  on Exception do
    result:=false;
  end;
end;

    
    函數unzipReportFiles的作用是將壓縮包中的文件解壓出來,供ACTIVEX使用。它與REPORT SERVER程序中的zipReportFiles剛好是個相反的過程。
      DesignReport(rpFileName:String)方法:

function TreportAForm. DesignReport (rpFileName:String) :boolean;
var
  dtfFileName,rpFileName:String;
  fldlist:TStringList;
  T: TStringField;
  i:Integer;
begin
  ……
  dtfFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.dtf',[rfReplaceAll, rfIgnoreCase]);//獲得數據環境文件名
  fldlist:=TStringList.Create;
  fldlist.LoadFromFile(dtfFileName);
  rpAdoquery.Fields.Clear;
  for i := 0 to fldlist.Count - 1 do
  begin
    T := TStringField.Create(nil);
    T.FieldName := fldlist[i];
    T.Name := rpAdoquery.Name + T.FieldName;
    rpAdoquery.Fields.add(T);
  end;
  FrReport1.LoadFromFile(rpFileName);
  FrReport1.DesignReport;
end;

    函數DesignReport先從.dtf(由REPORT SERVER生成)文件中恢復報表的數據環境,接着使用FASTREPORT的FrReport控件設計報表。在FASTREPORT中,對DataSet中的Field只關心名稱(全部通過Variant類型處理),而並不關心數據類型,所以恢復報表的數據環境時,所有字段都當作String類型加入。樣例假設報表只有一個名為rpAdoquery的DataSet。報表設計運行時窗口在ACTIVEX進程空間運行。

    用戶端設計好報表並保存后,需要將保存的報表文件(.frf)回送給服務器存儲。文件上傳對於大部分開發人員來說應該都是熟悉而簡單的,該部分程序本文就省略了。
    PrintReport(rpFileName:String)方法:

function TreportAForm. PrintReport (rpFileName:String) :boolean;
var
  repFileName:String;
begin
  ……
  repFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),'.frp',[rfReplaceAll, rfIgnoreCase]);//獲得已准備的報表文件名
  try
    frPreview1.clear;
    FrReport1.Preview:=nil;
    FrReport1.clear;
    FrReport1.LoadPreparedReport(repFileName);
    FrReport1.Preview :=frPreview1;
    FrReport1.ShowPreparedReport;
    result:=true;
  except
  on Exception do
    result:=false;
  end;
end;

    函數PrintReport裝入由REPORT SERVER運行好的報表.frp文件,通過調用FrReport的ShowPreparedReport方法在ACTIVEX端預覽和打印。

    方案實現方法的介紹結束。本方案具有的優點為:保持應用的結構形式不變(B/S),將C/S應用結構下已非常成熟的報表方案移植過來,使得在WEB應用中也可實現任意復雜的報表設計和打印,以及對打印效果進行精確控制。

http://www.cnblogs.com/sonicit/archive/2008/04/18/1160379.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM