Delphi报表制作技巧20篇 - 图文

更新时间:2024-04-28 05:33:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Delphi报表制作技巧 目 录 Delphi 3中报表的制作................................................................................................................................................ 2 Delphi编写一个打印程序, ........................................................................................................................................ 3 Delphi应用程序中中国式报表的制作........................................................................................................................... 3 打印测试................................................................................................................................................................... 6 Quick Report 2.0 ......................................................................................................................................................... 7 quickreport................................................................................................................................................................10 把查询信息输出到word打印 .....................................................................................................................................11 财务报表..................................................................................................................................................................12 格式化整数输出。 ....................................................................................................................................................15 Delphi编写一个打印程序 ..........................................................................................................................................16 打印功能的实现........................................................................................................................................................17 动态制作报表 ...........................................................................................................................................................19 多栏打印..................................................................................................................................................................20 检测存在打印机........................................................................................................................................................20 精确打印输出的程序实现 ..........................................................................................................................................21 Delphi应用程序中中国式报表的制作..........................................................................................................................23 用Delphi编写打印程序的窍门 ...................................................................................................................................26 用Delphi实现打印功能 .............................................................................................................................................26 网络远程报表 ...........................................................................................................................................................27 中国式报表 ..............................................................................................................................................................29 Delphi 3中报表的制作 Borland Delphi是当今优秀的Windows可视化开发工具之一,今年5月份推出的Delphi3是一个真正的32位开发平台,比Delphi2增加了不少定制控件。Delphi 3没有捆绑ReportSmith报表制作工具,但同时增加了Qreport组中报表制作控件的功能,使制作报表更加方便灵活。在Delphi3中除了可以采用Qreport组中报表制作控件来制作报表外,还可利用打印机画布直接向打印机输出报表。下面将详细介绍这两种方法。 用打印机画布制作报表 Delphi引进了画布的概念,使向打印机输出和向屏幕的输出具有相同的方法,直接向打印机的画布输出线条和字符串,即可制作出任意规格的报表。首先,把支持打印机的Printers单元手工加到uses域里,然后在打印事件中调用Tprinter对象来制作报表。下面我们来看一个实际例子。假设在当前Form1上有一按钮名为Print-rep;在Dialogs控件组选取一PrintDialog控件加到Form1中,命名为printdg1;在Additional控件组中选取一StringGrid控件加到Form1中,命名为temsgrid1,设其网格为10行10列,并假设已完成了在temsgrid1控件中显示一10行10列的数据库表数据的准备工作,然后在Print—repClick事件中打印报表。程序示例如下: procedure TForm1.Print-repClick(Sender:Tobject); var I,j,cp,ph,pw,w,h:integer; beginif printdg1.Execute then //打开打印对话框 begin for cp:=1 to printdg1.copies do //打印份数 begin with printer do begin orientation:=poPortrait; //设置纵向打印方式 title:=‘打印报表例子’;//打印任务标题 pw:=pagewidth;//获得当前设置的打印纸页宽 ph:=pageheight; //获得当前设置的打印纸页高 h:=2104; w:=1488; begindoc;//开始打印 //下面画表格,先画11条横线 for I:=1 to 11 do begin canvas.moveto(100*pw div w,I*100*ph div h); canvas.lineto(1100*pw div w,I*100*ph div h); end; //再画11条竖线 for I:=1 to 11 do begin canvas.moveto(I*100*pw div w,100*phdiv h); canvas.lineto(I*100*pw div w,1100*ph div h); end; //填上数据 for j:=1 to 10 do for I:=1 to 10 do canvas.textout((100*I+10)*pwdiv w,(100*j+10)*ph div h,temsgrid1.cells[I -1,j-1]); enddoc;//结束打印 end; end; end; end; 在上面程序中,先打开打印对话框,然后再设置打印方向、打印标题并获取当前打印纸的宽高点数。在程序中,为了使在不同打印分辨率中有相同的打印外观,我们以180×180分辨率的A4纸点数2104×1488做为基准换算打印坐标。Printer.Canvas.Moveto(x,y)用于移动打印机画布坐标到(x,y)处;Printer.Canvas.Lineto(x,y)用于从画布当前坐标处画一直线至(x,y);Printer.Canvas.Textout(x,y,Text)用于在画布的坐标(x,y)处以当前字体输出字符串 Text。为了使报表更加美观和生动,可以设置打印机画布的字体、大小,还可以插入图片等。 用Qreport组控件制作报表 QuickReport是Quick Soft Development公司专为Delphi设计的用于制作报表的工具。 Delphi3中Qreport组控件十分丰富,只要很好地利用这些控件就能制作出效果不错的报表来。 1.建立一个报表 (1)首先我们在Form2上放置一TQuickRep控件,TQuickRep控件是建立一报表必不可少的控件。可以改变该控件的属性设置,以使其外观符合要求。在缺省情况下,TQuickRep控件的尺寸为放置在窗口上的尺寸,TQuickRep控件根据当前控件的尺寸来自动设置数据区的范围。 改变TQuickRep控件外观最简单的方法是:在TQuickRep控件内单击鼠标右键,在弹出菜单中选择Report Settings项来

2

打开设置窗口。 (2)设置数据。把TQRBand控件放到TQuickRep控件上,并把BandType属性设为rbTitle,缺省情况下Align属性是a1Top。然后把TQRLabel控件放到TQRBand控件上,修改它的Caption属性,作为报表的标题。 (3)把数据放到报表上。首先再将一个TQRBand控件放到TQuickRep控件上,并把BandType属性设为rbDetail,然后把一个Ttable控件(名为Table1)放到From2上,设置它的DataBaseN ame属性和TableName属性,并把Ttable控件的Active属性设为True。与Delphi2不同的是,Delphi3中TQuickRep控件没有DataSource属性而以DataSet属性来代替,因此设置TQuickRep控件的DataSet属性值为Table1。 (4)把一个TQRDBText控件放到第二个TQRBand控件上,设置它的DataSet属性值为Table1,设置DataField属性为要显示的字段。 到此,就可在TQuickRep内单击鼠标右键,在弹出菜单中选择Review项来预览报表。 2.预览及打印报表 在运行期间也可以调用TQuickRep的Preview方法来预览报表,在预览窗口中可单击打印按钮来打印报表,但也可直接调用TQuickRep的Print方法来打印报表。 3.给报表的数据画上网格 在省缺情况下,报表的数据周围是没有网格的。我们可以给数据周围加上网格,使其看起来更像一个表格,方法是设置TQRBand控件的高度与TQRDBText高度控件相同,将所有的TQRDBText控件的AutoSize属性设为False,并将它们首尾相连,然后将每一个TQRDBText控件的Frame的子属性DrawBottom、DrawLeft、DrawRight、DrawTop设为True,子属性Style设为poSolid。这样即可加上网格线。 Qreport控件组共有16种报表制作工作,以上只是一个粗略的介绍,不能尽述其强大的功能。在具体应用中,用户可以通过加载其它报表制作控件设计出有个性的报表来。 两种方法的比较 以上两种制作报表的方法各有千秋。用Qreport组控件来制作报表的方法比较简单,但是当报表较多时,用此方法显得较笨拙,并且使编译后的可执行文件急剧增大。用打印机画布制作报表的方法比较繁重,必须根据实际的报表来设计每一条线的起止坐标,算好每个坐标点的值,但当报表较多时,用此方法显得较灵活,并且使编译后的可执行文件的代码量增加不多,因此在这种情况下建议选用此方法 Delphi编写一个打印程序, 如果你想自己用那么,下面这些技巧或许对你有所帮助。 1.获娶显示当前打印机的分辨率 Windows下的打印分辨对打印程序有着至关重要的作用,如果你想知道打印机的分辨率,请在程序中加入一行:ShowMessage(′水平分辨率′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSX))+chr(13)+′垂直分辨率:′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSY)));结果就一目了然了。 2.将结果直接送到打印机 Delphi提供了两种打印方式:一是将结果输送到Form,再调用Form的print方法将结果输送到打印机,二是将结果直接输送到打印机。如果你采用第一种方式,则无论你怎样调整Form的PrintScal属性,打印出来的东西也不会让你满意。因此建议采用第二种方式。 3.尽量不要使用AssignPrn 尽管AssignPrn简化了文本打印操作,使输出到打印机像输出到文件一样简单。但简单带来的是一系列的不方便:你无法知道当前打印的行数,无法准确控制行距,无法灵活改变字体字形等等。还是用打印机的Canvas属性进行打印吧。 4.用打印机的点数做度量单位 如果想让打印程序在任何打印机上都能正常地打印,你就必须改变你的度量单位。如果采用固定的度量,不同分辨率的打印效果是不同的。举例来讲:printerCanvasrectangle(0,0,360,720)在360×360的佳能4200SP上能打出一个1英寸宽、2英寸高的矩形,但在600×600的惠普6L上只能打出06英寸宽、12英寸高的矩形。使用打印机的点 数做为度量单位是一个明智的选择。具体做法如下: VarPointX,PointY:integer;PointX:=GetDeviceCaps(printer Handle,LOGPIXELSX);PointY:=GetDeviceCaps(printerHandle,LOGPIXELSX);printer Canvasrectangle(0,0,PointX*1,PointY*2)这样,无论你使用什么样的打印机,都能得到一个1英寸宽、2英寸高的矩形。 5.添加打印程序单元 尽管Delphi在生成窗体时会自动在USES部分加入许多程序单元,但打印程序单元(Printers) 却不在之列,要想使打印机正常工作和程序不出错,你还是老老实实手工给它加上吧。 Delphi应用程序中中国式报表的制作 在众多可视化数据库开发工具中, Delphi以其真正的面向对象、高效率、支持多层结构应用开发、支持多层B/S结构开发等优良特性脱颖而出,成为广大编程人员的首选开发工具。

3

在数据库应用程序开发中,系统设计员、程序设计员需要考虑的一个重要问题是如何设计和输出报表,在Delphi中我们可以采用多种方案来解决这一问题,如运用OLE自动化技术将数据输出到MS-WORD、MS-EXCEL中等,但其中最直接、最本地化的还是使用Delphi3.0/40中的QuickReport报表组件,它是挪威QuSoft公司专门为Delphi 编写的,使用QuickReport可以迅速设计出符合西方人习惯的报表。 然而,在设计中国式报表时,笔者发现在QuickReport中设计列与列之间的竖线和斜线比较困难;虽然QuickReport提供了TQShape控件,使用该控件可以画出列与列之间的竖线,但如果用户不能正确调整TQShape实例的高度,输出报表中的竖线不是不连续就是超长,另外如果我们调整了某个Band的高度,我们将不得不调整该Band下的所有TQShape实例的高度;至于斜线,QuickReport报表组件根本就没有提供这一功能。 笔者认真查找了有关资料,成功地解决以上问题,并愿意将解决方法与大家共享,希望能对大家有所帮助。 1、 解决思路 以TQShape为父类,建立新的控件,新控件可以画竖线、斜线和反斜线。重载TQShape类的Paint方法,这样在设计阶段可以非常直观地画斜线、反斜线和竖线,用户可以在设计阶段选择线的类型,如果选择直线,控件自动将其高度调整为所属Band的高度,用户可以调整其横向位置但不能调整其高度;如果选择斜线,用户可以根据需要调整斜线的长度和倾角。重载TQShape类的Print方法,这样可以在运行阶段输出直线和斜线。 说明:该控件只能画直线和斜线,如果读者需要画矩形和园,可以使用TQShape控件来实现。 2、控件设计步骤 步骤1、使用Delphi提供的控件向导,选择TQShape为父类,建立新类TMyQRShape,并选择适当的包(Package),最后生成单元文件。 步骤2、在生成的单元文件中,增加枚举类型, Tlines = ( None,TopBottom,BottomTop ) ; None、TopBottom、BottomTop三种取值,分别代表直线、斜线 和反斜线 / 。 步骤3、在新类TMyQRShape 中增加private 成员 FLineType:Tlines ,增加published属性 LineType:Tlines Read FLineType Write SetFLineType 。 步骤4、建立过程SetFLineType 。 procedure TMyQRShape.SetFLineType(Value:Tlines); begin if Value<>FLineType then begin FLineType:=Value ; Invalidate ; end ; end ; 步骤5、重载Paint方法 procedure TMyQRShape.Paint ; begin case LineType of BottomTop: begin Canvas.MoveTo(0,Height) ; Canvas.LineTo(width,0 ) ; end ; TopBottom: begin Canvas.MoveTo(0,0) ; Canvas.LineTo(width,Height ) ; end ; None: begin Height := Parent.Height ; Top:=0 ; Width:=4 ; Shape:=qrsVertLine ; Inherited Paint ; end ; end ; end ; 步骤6、重载Print方法 procedure TMyQRShape.Print(OfsX,OfsY : Integer); begin with QRPrinter do begin case LineType of BottomTop:

4

begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)+Height) Canvas.LineTo(Xpos(OfsX + Size.Left)+width,Ypos(OfsY + Size.Top) ) ; end ; TopBottom: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)) ; Canvas.LineTo(Xpos(OfsX + Size.Left)+Width,Ypos(OfsY + Size.Top)+Height ) ; end ; None: Inherited Print(OfsX,OfsY ) ; end ; end ; end; 步骤7、保存并安装TMyQRShape控件。 本控件在Delphi40下调试、安装,并成功应用于某数据库管理系统的开发。该控件的完整代码如下。 源程序: unit MyQRShape; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, QuickRpt, Qrctrls; type Tlines = ( None,TopBottom,BottomTop ) ; TMyQRShape = class(TQRShape) private FLineType:Tlines ; procedure SetFLineType(Value:Tlines) ; protected procedure Print(OfsX, OfsY : integer); override; procedure Paint ;Override ; public published property LineType:Tlines Read FLineType Write SetFLineType ; end; procedure Register; implementation procedure TMyQRShape.SetFLineType(Value:Tlines); begin if Value<>FLineType then begin FLineType:=Value ; Invalidate ; end ; end ; procedure TMyQRShape.Paint ; begin case LineType of BottomTop: begin Canvas.MoveTo(0,Height) ; Canvas.LineTo(width,0 ) ; end ; TopBottom: begin Canvas.MoveTo(0,0) ; Canvas.LineTo(width,Height ) ; end ; None: begin

5

Height := Parent.Height ; Top:=0 ; Width:=4 ; Shape:=qrsVertLine ; Inherited Paint ; end ; end ; end ; procedure TMyQRShape.Print(OfsX,OfsY : Integer); begin with QRPrinter do begin case LineType of BottomTop: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)+Height) Canvas.LineTo(Xpos(OfsX + Size.Left)+width,Ypos(OfsY + Size.Top) ) ; end ; TopBottom: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)) ; Canvas.LineTo(Xpos(OfsX + Size.Left)+Width,Ypos(OfsY + Size.Top)+Height ) ; end ; None: Inherited Print(OfsX,OfsY ) ; end ; end ; end; procedure Register; begin RegisterComponents(`Qreport`, [TMyQRShape]); end; 打印测试 请各位参考以下程序内容: unit Passthru; interface uses printers, WinProcs, WinTypes, SysUtils; Procedure PrintTest; implementation Type TPassThroughData = Record nLen : Integer; Data : Array[0..255] of byte; end; Procedure DirectPrint(s : String); var PTBlock : TPassThroughData; Begin PTBlock.nLen := Length(s); StrPCopy(@PTBlock.Data,s); Escape(printer.handle, PASSTHROUGH,0,@PTBlock,nil); End; Procedure PrintTest;

6

Begin Printer.BeginDoc; DirectPrint(CHR(27)+'&l1O'+'Hello, World!'); Printer.EndDoc; End; end. Quick Report 2.0 Quick Report 2.0 中 提 供 的 默 认 打 印 预 览 窗 口 是 英 文 界 面 的, 如 果 开 发 的 中 文 软 件 中 带 有 这 种 英 文 显 示, 不 免 有 些 小 小 的 缺 憾。 因 此 有 必 要 实 现 中 文 界 面 的 打 印 预 览 窗 口。 但 是Delphi 提 供 的 源 代 码 中 并 没 有 打 印 预 览 窗 口 的.PAS 源 文 件, 这 就 无 法 直 接 修 改 源 码, 只 能 全 部 自 己 编 程 实 现。 经 多 次 实 践, 笔 者 模 仿 实 现 了 与 默 认 预 览 窗 口 外 观 类 似, 功 能 相 同 的 打 印 预 览 窗 口。 步 骤 如 下: 1. 新 建 一 个 窗 体, 设 置Name 为MyPreview。 2. 在 窗 体 上 添 加 一Toolbar 控 件, 模 仿 默 认 预 览 窗 口 创 建 相 应 的Toolbutton, 并 设置 各 按 钮 的Hint 提 示。 3. 添 加 一Panel 控 件, 对 齐 方 式 置 为alBottom。 再 在 此Panel 上 放 一ProgressBar( 左 对 齐)和Panel( 右 对 齐), 分 别 显 示 报 表 装 载 进 度 和 其 他 提 示 信 息。 4. 添 加QRPreview 控 件, 对 齐 方 式 置 为alClient。 5. 添 加OpenDialog, 设 置Filter 属 性 为*.QR; 添 加SaveDialog, 设 置Filter 属 性 为*.QR|*.TXT|*.HTM|*.CSV, 设 置DefaultExt 属 性 为*.QR。 6. 双 击 各 个Toolbutton, 输 入 相 应 代 码。 可 以 按 以 下 方 法 调 用 自 定 义 预 览 窗 口。 重 载TQuickRep 的OnPreview 事 件, 输 入 如 下 代 码: procedure TRptForm.RptFormPreview(Sender: TObject); begin with TMyPreview.Create(Application) do begin QRPreview1.QRPrinter := TQRPrinter(Sender); CurRep := self; Show; end; end; 附 各 成 员 方 法 的 具 体 实 现: unit Myprv; interfaceuses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, ToolWin, qrprntr,Quickrpt, StdCtrls, ExtCtrls,qrextra,qrhtml; type TMyPreview = class(TForm) QRPreview1: TQRPreview; ToolBar1: TToolBar;( 其 余 成 员 变 量 和 成 员 方 法 声 明 略。) private { Private declarations } FPageCount : integer; // 生 成 报 表 的 总 页 数 public { Public declarations } CurRep : TQuickRep; // 所 预 览 的 报 表 procedure UpdatePanelShow; end;

7

implementation{$R *.DFM} procedure TMyPreview.UpdatePanelShow; begin // 更 新 显 示 Panel2.Caption := ' 第 '+inttostr(QRPreview1.PageNumber)+ ' 页 总 '+inttostr(FPageCount)+' 页'; end; procedure TMyPreview.ToolButton2Click(Sender: TObject); begin //ZoomToFit 缩 放 至 全 屏 QRPreview1.ZoomToFit; end; procedure TMyPreview.ToolButton3Click(Sender: TObject); begin //ZoomTo100% 缩 放 至 实 际 大 小 QRPreview1.Zoom:=100; end; procedure TMyPreview.ToolButton4Click(Sender: TObject); begin //ZoomToWidth 缩 放 至 页 宽 QRPreview1.ZoomToWidth; end; procedure TMyPreview.ToolButton6Click(Sender: TObject); begin //First page QRPreview1.PageNumber := 1; UpdatePanelShow; end; procedure TMyPreview.ToolButton8Click(Sender: TObject); begin //prior page Q RPreview1.PageNumber := QRPreview1.PageNumber+1; UpdatePanelShow; end; procedure TMyPreview.ToolButton7Click(Sender: TObject); begin //next page QRPreview1.PageNumber := QRPreview1.PageNumber-1; if QRPreview1.PageNumber =0 then QRPreview1.PageNumber:=1; UpdatePanelShow; end; procedure TMyPreview.ToolButton9Click(Sender: TObject); begin //Last page QRPreview1.PageNumber := FPageCount; UpdatePanelShow; end; procedure TMyPreview.QRPreview1PageAvailable(Sender: TObject; PageNum: Integer); begin //get pagecount FPageCount := PageNum ; UpdatePanelShow;

8

end; procedure TMyPreview.FormClose(Sender: TObject; var Action: TCloseAction); begin CurRep := nil; Action := caFree;end;procedure TMyPreview.ToolButton14Click(Sender: TObject); begin //close the window Close; end; procedure TMyPreview.ToolButton11Click(Sender: TObject); begin //print setup QRPreview1.QRPrinter.PrintSetup; end; procedure TMyPreview.ToolButton13Click(Sender: TObject); begin //print QRPreview1.QRPrinter.Print; end; procedure TMyPreview.ToolButton16Click(Sender: TObject); begin //save button if SaveDlg1.Execute then begin if (SaveDlg1.FIlterIndex <>1)and(CurRep = nil)then Exit; case SaveDlg1.FilterINdex of //--*.QR 1: QRPreview1.QRPrinter.Save(SaveDlg1.FileName); //--*.TXT 2: CurRep.ExportToFilter(TQRAsciiExportFilter.Create(SaveDlg1.FileName)); //--*.HTM 3: CurRep.ExportToFilter(TQRHTMLExportFilter.Create(SaveDlg1.FileName)); //--*.CSV 4: CurRep.ExportToFilter(TQRCSVExportFilter.Create(SaveDlg1.FileName)); end; end; end; procedure TMyPreview.ToolButton17Click(Sender: TObject); begin //load button if OpenDlg1.Execute then QRPreview1.QRPrinter.Load(OpenDlg1.FileName); end; procedure TMyPreview.QRPreview1ProgressUpdate(Sender: TObject; Progress: Integer); begin //updage progress bar ProgressBar1.Position := Progress; end; procedure TMyPreview.SaveDlg1TypeChange(Sender: TObject); begin //set DefaultExt property of Savedialog with SaveDlg1 do case FilterIndex of 0: DefaultExt := '.QR'; 1: DefaultExt := '.TXT'; 2: DefaultExt := '.HTM'; 3: DefaultExt := '.CSV'; end; end; end.//end of Unit

9

以 上 程 序 在Windows NT 4.0 中 文 版(Service Pack 3),Delphi 3.0 环 境 下 调 试 通 过。 quickreport Delphi中的quickreport是挪威的QuSoft AS公司专门为Delphi设计的用于制作报表的组件,具有很强的访问数据库的能力。但是,从我个人的使用经验来看,这个组件的帮助写的不够详尽,例程也不多。因而一般编程人员都是在设计期间设计好报表的所有格式,并在运行时连上数据源(即delphi中的dataset)运行程序的。显然,这种方法不具备很强的灵活性。在这里我想向大家介绍一种在程序运行时如何动态制作报表的方法。 ---- 首先,建立form1;unit1(调用窗体),其中放入button1:caption为‘预览’,用以查看报表,再加入label1:caption为‘报表标题’;edit1用以接收用户输入的标题。再建立form2(放置quickreport元件的窗体),其中放入quickrep元件和三个qrband元件,其bandtype分别为rbTitle,rbColumnHeader,rbDetail.但其中无须放入qrlabel和qrdbtext元件,由程序中自动生成。再在form2;unit2中放入table1,table2。在databasename属性中填入数据库所在的Alias Name.在tablename中分别填入Goods,TableInformation.其中Goods中存放商品信息,可以设置以下字段:ID;Name;Unit;Price。TableInformation中的字段为TableName; FieldsName; DisplayName数据库中填入以下信息。 Goods ID 商品编号 Goods Name 商品名称 Goods Price 商品单价 好了,准备工作完成了一半,现在写入代码。在unit1的implementation下写入uses Unit2;在button1的click事件中写入以下代码: begin PreparePrint; with Form2 do begin Preview; Table1.Close; Table2.Close; end; end; 在unit1中添加一个过程PreparePrint; procedure TForm1.PreparePrint; var LeftValue:integer; aLabel:TQRLabel; aText:TQRDBText; begin LeftValue:=100; with Form2 do begin aLabel := TQRLabel.Create(Form2); '设置标题 aLabel.Parent := TitleBand1; with aLabel do begin Width:=300; Height:=TitleBand1.Height; Alignment:=taCenter; Caption := Edit1.Text; end; Table1.Open; Table2.Open; Table2.First; while not Table2.EOF do begin aLabel := TQRLabel.Create(Form2); ’设置显示字段标题 aLabel.Parent := HeaderBand1; with aLabel do begin AutoSize:=False; AutoStretch:=True; Left :=LeftValue; Top := 0; Width:=80; Height:=HeaderBand1.Height; Alignment:=taCenter; Caption := ' '+table2.FieldbyName( 'DisplayName').AsString+' ';

10

end; aText := TQRDBText.Create(Form2); '设置显示字段内容 aText.Parent := DetailBand1; with aText do begin AutoSize:=False; AutoStretch:=True; Height:=DetailBand1.Height; DataSet:=Table1; DataField:=Table2.FieldbyName( 'FieldsName').AsString; Alignment:=taCenter; end; LeftValue:=LeftValue+80; Table2.Next; end; end; end; ---- 好了,运行程序可以看到报表预览的效果,显示了Goods中的ID,Name,Price。在运行时完全可以控制table1和table2中的数据内容。显示不同数据库的不同信息,改变table2中的记录顺序可改变报表中的显示顺序。而且,由于报表的各部分,甚至包括各band和system data均可在程序运行时依据上述方法动态生成。其属性也可动态设置。所以我们完全可以制作一个类似于Dephi中Form Wizard一样的报表精灵,还可提供一个灵活而且强大的打印设置功能。 ---- 以上代码在delphi3中运行通过。 把查询信息输出到word打印 我喜欢用EXECEL输出,这是我文档管理的报表代码,DELPHI可以直接调用EXECEL对象 WORD行的,不过我这里没资料有EXECLE的,因为EXECEL就是做报表的嘛 记得USE里用到comobj, 在说下两个变量v,sheet:variant;是窗体类里申明了的 procedure Tfmsearch.expbtClick(Sender: TObject); var range:variant; i,j:integer; begin if dm1.qrinfo.RecordCount=0 then begin MessageBox(handle,Pchar('没有记录可输出'),'输出提示',0+48); exit; end; v:=createoleobject('excel.application'); v.workbooks.add; v.workbooks[1].worksheets[1].name:='文章清单'; sheet:=V.workbooks[1].worksheets[1]; v.visible:=true; range:=sheet.range['A1:E1']; range.merge; range.borders.linestyle:=0; range.formular1c1:='文 章 清 单'; range.horizontalalignment:=3; range.verticalalignment:=2; range.characters.font.name:='宋体'; range.characters.font.fontstyle:='加粗'; range.characters.font.size:=15; range.characters.font.colorindex:=2; range.interior.color:=$00bbbbbb; // range:=sheet.range['A2:E2']; range.merge;

11

range.borders.linestyle:=0; range.formular1c1:='输出用户:'+curuser+ ' 输出日期:'+formatdatetime('yyyy\年\月\日\range.horizontalalignment:=3; range.verticalalignment:=2; range.characters.font.name:='宋体'; range.characters.font.size:=10; range.characters.font.colorindex:=2; range.interior.color:=$00Bbbbbb; // sheet.cells[3,1]:='编号'; sheet.cells[3,2]:='文章名称'; sheet.cells[3,3]:='录入日期'; sheet.cells[3,4]:='录入用户'; sheet.cells[3,5]:='保密性'; // for i:=1 to 5 do begin sheet.cells[3,i].borders.linestyle:=1; sheet.cells[3,i].horizontalalignment:=3; end; dm1.qrinfo.First; j:=3; while not dm1.qrinfo.Eof do begin inc(j); sheet.cells[j,1]:=dm1.qrinfotextid.AsString; sheet.cells[j,2]:=dm1.qrinfotextname.AsString; sheet.cells[j,3]:=dm1.qrinforecdate.AsDateTime; sheet.cells[j,4]:=dm1.qrinforecuser.AsString; sheet.cells[j,5]:=dm1.qrinfotextsec.AsString; dm1.qrinfo.next; if dm1.qrinfo.Eof then break; end; range:=sheet.range['A4:'+'E'+inttostr(j)]; range.borders.linestyle:=1; // sheet.cells.entirecolumn.autofit; sheet.pagesetup.printtitlerows:='$1:$3'; sheet.pagesetup.rightheader:='execel报表'; sheet.pagesetup.leftheader:='共'+inttostr(dm1.qrinfo.RecordCount)+'篇文章'; sheet.pagesetup.centerfooter:='共&N页'+'/第&P页'; sheet.pagesetup.centerhorizontally:=true; sheet.pagesetup.orientation:=1; sheet.pagesetup.draft:=false; sheet.pagesetup.blackandwhite:=true; sheet.pagesetup.zoom:=100; v.displayalerts:=false; sheet.printpreview; end; 财务报表 1、哪个单位的财务科都有几本厚厚的台帐,我院财务科提出打印药库的台帐,好脱掉手工帐,也算是办公现代化了,

12

要求合情合理,院长也说应当如此。看着财务科提供的红绿相间的、统一印制的、行间距3毫米的台帐专用小卡片,我不由想到中国人节俭的美德。考虑到每个药品至少打印一张,而且表格线一个都不能少,加上中医院药品有二千多种,所以这个报表必须用激光打印机来打印(至于如何让院长同意购买激光打印机,则是另一回事,办公自动化哪能没有代价呢)。程序实现的思路基本上是把打印纸当做画布在上面画一个个小矩形,具体由以下几个过程实现:(设Form名为Ttzview)。 先在程序中定义二个类:titlerecord=record Currect:Trect; Atitle:string;end;detailrecord=record Arect:Trect; alignment:word;end; ---- 再建立以下四个过程; RectDraw(Acanvas:Tcanvas;s:string;R:Trect;frame:boolean;position:word); 这个过程就是画矩形用的,frame决定是否画线(这里当然要画啦!); printinitit(firsttop,firstleft:integer); 这个过程是初始化用的,就是定义各个小矩形在纸上的位置; printtitle(acanvas:Tcanvas); 这个过程是打印表头的,因为表头的字总是要大一些; printdetail(acanvas:Tcanvas;Qtz:Tdataset;acount:integer;firstpage:integer); 这个过程才是打印具体内容的,一页打印40行,不够就换页,总之一页只能是一个药品; printhj(acanvas:Tcanvas); 一看就知道,这个过程就是打印合计的啦! 只要建个Botton,顺序调用printinitit,printtitle,printdetail,printhj这四个过程就行了, (调用格式:printtitle(printer.canvas);) 对了,Qtz就是查询出来的台帐数据,如何生成的这里就不谈了。 此程序在delphi1,delphi3版本WIN3.2,WIN95,WIN97,WIN98平台下运行通过,顺便提供台帐数据库的库结构 2、非常复杂,无法取巧的报表 我院肿瘤科需要打印病人的病案,这可是特色专科现代化建设的一部分,卫生局要来检查的,所以必须完成任务。每一个病案有100多个项目,若用Qreport,中间如果要加减一个项目(这事常有),几十个项目的调整排列会使人昏倒。我一下子建立100多个临时变量,在虚拟的画布上画啊画,运行在我的PII233,64M内存的机子上倒是顺顺当当的,不过换到肿瘤科的486,8M内存的机子上时(大家别笑),系统堆栈马上溢出,所以只好祭出指针大法(我的编程水平好象又有长进,窃喜),方法如下: 先建立titleprint类: titleprint=^Titlerecord;titlerecord=record Currect:Trect; Atitle:string[50];end;再建立过程printnow(Form的名称叫zlk,printdot就是打印机的点数,一般针打是180);procedure Tzlk.printnow(acanvas:Tcanvas);var i,x,y,pc_count:integer; myprint:array[0..200] of titleprint;begin firsttop:=round(int(0.5/2.54*printdot)); firstleft:=round(int(0.1/2.54*printdot)); rowheight:=round(int(0.7/2.54*printdot)); x:=0+firstleft;y:=round(int(1.3/2.54*printdot))+firsttop; pc_count:=0; inc(pc_count); new(myprint[pc_count]); myprint[pc_count]^.currect:=rect(x+round(int(0.1/2.54*printdot))+firstleft,y,x+round(int(3.0/2.54*printdot))+firstleft,y+firsttop+round(int(0.5/2.54*printdot)));myprint[pc_count]^.atitle:=Lname.caption+DBname.text;ACanvas.MoveTo(myprint[pc_count]^.currect.left, myprint[pc_count]^.currect.top-round(rowheight/5));{下面的四行还要重复100多次,基本差不多,就不都写出来赚稿费了}inc(pc_count); new(myprint[pc_count]);myprint[pc_count]^.currect:=scalerect(myprint[pc_count-1]^.currect,round(int(2.5/2.54*printdot)),0); myprint[pc_count]^.atitle:=Lxb.caption+Cxb.text; ACanvas.LineTo(myprint[i]^.currect.right, myprint[i]^.currect.top-round(rowheight/5)); ....... {打印} printtitle(acanvas); {这个函数就不提供了,表头不要也没关系} for i:=1 to pc_count do begin RectDraw(Acanvas,myprint[i]^.atitle,myprint[i]^.currect,false,dt_left or dt_singleline or dt_vcenter); end; dispose(myprint[pc_count]); {别忘了把指针占用的内存释放}end; 最后建个Botton,加个是否真的打印的判断,再调用这几个函数就行了。 (调用格式:printtitle(printer.canvas);) 看了以上两个例子,是不是觉得WINDOWS下的打印其实很简单,就跟你手工画表一样,而且表格内容的位置是居中、居左、还是居右,全由打印内容的Alignment决定,打印格式由内容的Display Format决定,用不着自己去算,方便极了。 报表 一、问题的提出 ---- Delphi作为强大的数据库开发工具,正被愈来愈多的编程人员所采用,\聪明的程序员用Delphi\更形象生动的道出广大程序员的心声,但这并不意味着所有功能的实现都非常容易,例如,笔者在开发军队的某个信息系统中,就在为数据分析模块中DecisionGrid1控件的数据进行报表输出时走了不少的弯路。广大的Delphi的爱好者在今后的学习或工作中也有可能会遇到类似的问题,而在许多参考书中,很少有甚至没有关于它们的解决方法,于是,我想花费一点时间把它整理出来,以供大家参考。本文中报表动态生成的公用模块具有很大的灵活性和易操作性,其中的思路、实现的功能和通用性等方面的优缺点就由大家看了本文后自有定论。

13

二、建立报表的动态输出公用模块 ---- 下面,结合公司人事管理信息系统说明其实现的方法和技术。 ---- 1、基本思路:首先从DecisionGrid1中获得报表所需数据,放到二维数组PA中,然后在C:\\DataWork中动态创建一个数据表tjb.dbf,存放报表数据,最后用T able1与tjb.dbf相连接,以后工作就与一般的动态输出报表(如查询报表)相类似,在这里我就无须赘述了。 ---- 2、建立窗体文件:放入六个用于数据分析的常用控件DecisionQuery1、DecisionSource1、DecisionCube1、DecisionGraph1、DecisionPivot1、DecisionGrid1,设置DecisionSource1的decisionCube属性为decisionCube1,decisionCube1的Dataset属性为decisionQuery1、decisionQuery1的DatabaseName属性为c:\\datawork;一个Table1控件,用于连接数据表tjb.dbf;一个QuickRep1控件,用于数据的报表输出;两个Button1和Button2控件,其Caption分别设为\报表输出\和\返回\。分别设置decisionCube1的Dataset属性为decisionQuery1、decisionQuery1的DatabaseName属性为c:\\datawork.。 ---- 3、单元文件的主要控件代码 Button1控件的代码如下(定义变量部分略),主要分以下8个功能块来加以说明: ---- ⑴删除同名或上一次建立的数据表 if FileExists('c:\\DataWork\\tjb.dbf') then deletefile('c:\\ DataWork \\tjb.dbf'); ---- ⑵根据DecisionGrid1控件的cells属性,获得报表所需数据,并将其默认的'Sum'值汉化成'总计'、'合计'、'小计'以符合汉语的习惯要求,所求得的数据存放于二维数组PA中 for i:=1-DecisionGrid1.FixedCols to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 dofor j:=0-DecisionGrid1.FixedRows to DecisionGrid1.RowCount-DecisionGrid1.FixedRows-1 do beginpa[i,j]:=DecisionGrid1.cells[i,j];//处理DecisionGrid1控件中固定列的值为'Sum'的数据项if ((i=1-DecisionGrid1.fixedcols) and (pa[i,j]='Sum')) then pa[i,j]:='总 计' else if ((i = -1) and (pa[i,j]='Sum')) then pa[i,j]:='小 计' else if ((i<-1) and (i>1-DecisionGrid1 .FixedCols) and (pa[i,j]='Sum')) then pa[i,j]:='合 计';//处理DecisionGrid1控件中固定行的值为'Sum'的数据项 if (pa[i,j]='Sum' ) and (j=-1) thenpa[i,j]:='总 计';end; ---- ⑶用T able1动态创建数据表tjb.dbf Table1.Active:=false;with Table1 dobegin DatabaseName := 'c:\\DataWork'; TableName := 'tjb'; TableType := ttDBase; with FieldDefs do begin Clear; for i:=1 to 40 do Add(IntToStr(i),ftString,30, False); end; CreateTable;end;//下面将DecisionGrid1控件中的数据放入数据表中Table1.Active:=true;for j:=1-DecisionGrid1.FixedRows to DecisionGrid1.RowCount-DecisionGrid1.FixedRows-1 do begin K:=0; Table1.Insert;for i:=1-DecisionGrid1.FixedCols to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do begin Table1.Fields[K].AsString:=pa[i,j]; K:=K+1; end; Table1.Post; Table1.Next; end; ---- ⑷下面代码确定输出报表的每列宽度 SetLength(M,DecisionGrid1.ColCount);//动态设置数组copy(M,1-DecisionGrid1.FixedCols,DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1);//重新设置动态数组的起始位置for i:=1-DecisionGrid1.FixedCols to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do begin M[i]:=0;for j:=1-DecisionGrid1.FixedRows to DecisionGrid1.RowCount-DecisionGrid1.FixedRows-1 do IF M[i]< Length (Trim (PA[I,J]))*8 THEN M[i]:= Length (Trim (PA[I,J]))*8; end; ---- ⑸如果要求输出报表的列宽相同(除DecisionGrid1控件的固定列,下同),可将数据项的最大列宽作为输出报表的列度,如果不要求,可跳过下面代码 max:=0;for i:=0 to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do if m[i]>max then max:=m[i];for i:=0 to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do m[i]:=max;ZK:=0;//报表总宽for i:=1-DecisionGrid1.FixedCols to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do ZK:=ZK+M[i]+1; ---- ⑹判断报表的宽度,超宽?横向报表?还是纵向报表? if ZK>1123 then beginApplication.MessageBox('报表超宽,请调整再输出!','警告', 1);//输出对话框 exit; endelse if ZK>794 then QuickRep.Page.Orientation:=poLandscape //横向 elseQuickRep.Page.Orientation:=poPortrait;//纵向 ---- ⑺以下代码完成了动态数据报表,与一般的动态输出报表功能相类似, for i:=1 to QuickRep.Bands.TitleBand.ControlCount DO//取消系统对控件的控制,下同 QuickRep.Bands.TitleBand.RemoveControl (QuickRep.Bands.TitleBand.Controls[0]);for i:=1 to QuickRep.Bands.DetailBand.ControlCount DO QuickRep.Bands.DetailBand.RemoveControl (QuickRep.Bands.DetailBand.Controls[0]);SetLength(QRShape,DecisionGrid1.ColCount); //动态设置数组SetLength(QRDBText, DecisionGrid1.ColCount); //动态设置数组K:=0;//动态生成对象的数,Lx:=(QuickRep.Width-ZK)DIV 2;//生成对象的左坐标//报表的动态生成For j:=1-DecisionGrid1.FixedCols to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do begin QRShape[K]:=TQRSHAPE.Create(tj1);//自定义对象的创建(下同) QRShape[K].Parent:=QuickRep.Bands .DetailBand;// 自定义对象的父类对象(下同)

14

QRDBText[K]:=TQRDBText.Create(tj1); QRDBText[K].parent :=QuickRep.Bands.DetailBand; QRShape[K].LEFT:=Lx;//生成对象的左坐标 QRShape[K].WIDTH:=M[J]+2; //生成对象的宽度 QRShape[K].HEIGHT:=QuickRep.Bands .DetailBand.Height+1; //生成对象的高度 QRShape[K].TOP:=-1; //生成对象的纵坐标 QRDBText[K].WIDTH:=QRShape[K].WIDTH-10; QRDBText[K].Left :=QRShape[K].LEFT+1; QRDBText[K].HEIGHT:=QRShape[K].Height div 2; QRDBText[K].Top :=QRDBText[K] .Height div 2+QRShape[K].Top; QRDBText[K].AutoSize:=false; QRDBText[K].Alignment:=taCenter; //生成对象居中 QRDBText[K].DataSet:=Table1; QRDBText[K].DataField:=IntToStr(k+1); Lx:=Lx+M[J]+1; Inc(k); end;//动态生成报表的标题 Caption := TQRLabel.Create(tj1);Caption.Parent := QuickRep.Bands.TitleBand;Caption.Alignment:=taCenter;//标题居中Caption.Width:= Length (Trim (ptitle))*8; //标题的宽度Caption.Left:=(QuickRep.Width- Length (Trim (ptitle))*8)div 2; //标题的左坐标Caption.Height:=QuickRep.Bands.TitleBand.Height-1; //标题的高度Caption.Top:= 0;Caption.Caption:=ptitle; //标题的名称,ptitle为调用该公用模块时的输入参数QuickRep.DataSet:=Table1;QuickRep.Preview;⑻动态生成对象的内存释放k:=0;for j:=1-DecisionGrid1.FixedCols to DecisionGrid1.ColCount-DecisionGrid1.FixedCols-1 do begin QRShape[K].Free; QRDBText[K].Free; inc(k); end;Caption.Free;Table1.Active:=false;//关闭数据表end; ---- 以上程序是在Delphi 4.0中调试通过,其数据文件应放在C:\\DataWork,类型为DB或DBF。 三、应注意以下几个问题 ---- 1、QuickRep1的Bands中的HasColumnHeader、HasDetail、HasTitle三个属性必须设置为true; ---- 2、不能忘记公用模块中QuickRep对象的DataSet属性设置,即源代码中的QuickRep.DataSet:=Table1语句; ---- 3、动态生成组件的宽度计算必须放在定义其字体属性完成后进行; ---- 4、另外,动态数组给定的内存(即数组容量)以及指定动态数组的起始位置(不一定为0,根据DecisionGrid1控件的固定列确定)很重要,因为一方面当数据库很大时它会大大减少内存的消耗,另一方面便于操作该数组,大大增强了程序的灵活性和通用性。 ---- 5、如果让QRDBText控件的数据居中,必须先设置其AutoSize属性为false,然后才能设置其Alignment属性为taCenter。这一点往往容易忽略,直接设置Alignment属性为taCenter,往往达不到数据居中的目的。 四、结束语 ---- 当然,由于客户对数据报表的可能特殊要求,此公用模块或许不能完全解决。但是,作为公用模块,能实现实现代码的重复利用,提高我们开发程序的效率,当然可以在此模块的基础上进行一些修改或补充,以满足大多数用户的要求,用以下两点加以说明。 ---- 1、如果要对数据表的字段进行动态选择输出,则可以将动态产生的数据表字段传递到另一个窗体进行输出选择,然后根据你所选择的字段进行报表输出,源程序几乎无须修改即可实现。 ---- 2、若要改变数据分析的内容(即直方图的维)时,只须修改SQL语句即可,动态、静态均可,具体的操作也就无须我多言了。各位Delphi编程爱好者不妨一试。 格式化整数输出。 比较大的数字在输出时会显得不易阅读,在Delphi中显示带分节号的数字是相当简单的一件事,如下即可:xxxxx.caption:ΚFormatFloat(′#′,524667500)。 任意打印 有时我们要打印任意排列的表或往已经印好的登记表上对号入座写上数据时,可以新建一个窗体(假设为Form1),再把Form1的BorderStyle设为bsNone、AutoScroll设为True,接下来再创建一个新窗体(假设为Form2),再建个按钮Button1,编写代码: procedure TForm2.Button1Click(Sender: TObject); begin Form1.Width :=900; Form1.Height :=800; Form1.Print;

15

end; 接下来你在Form1上对应的位置写上数据,运行后按Button1就会一五一十的打印下来了。 打印机的分辨率问题? Printer Resolution ? 问: I want to know, wich way I have to program, so every print is equal. No matter what resolution of the printer (300 or 600 dpi). Since now I program: TextOut(DC, 150, 150...) on a 300 dpi printer. But on 600 dpi, everything looks a bit strange, (small). What Objects can I use to print corectly?? 答: I get the printer resolution (and unprintable are if desired) and then specify coordinates using decimal inches * resulution e.g. x = trunc(1.5 * ResX) at 300 dpi will begin at 360 * 1.5 or 540 pixels. Get Resolution and unprintable ResX := GetDeviceCaps(ACanvas.Handle,LOGPIXELSX); ResY := GetDeviceCaps(ACanvas.Handle,LOGPIXELSY); pOffsetX := GetDeviceCaps(ACanvas.Handle,PHYSICALOFFSETX); pOffsetY := GetDeviceCaps(ACanvas.Handle,PHYSICALOFFSETY); print something Printer.Canvas.TextOut(trunc(1.5 * ResX)-pOffsetX, trunc(2.5 * ResY)-pOffsetY,'Hello'); This is nice when printing data on an existing paper form as you can just measure it up in decimal inches. 用Delphi编写打印程序的窍门 湖北 杨德军 如果你想自己用Delphi编写一个打印程序,那么,下面这些技巧或许对你有所帮助。 1.获娶显示当前打印机的分辨率 Windows下的打印分辨对打印程序有着至关重要的作用,如果你想知道打印机的分辨率,请在程序中加入一行:ShowMessage(′水平分辨率′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSX))+chr(13)+′垂直分辨率:′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSY)));结果就一目了然了。 2.将结果直接送到打印机 Delphi提供了两种打印方式:一是将结果输送到Form,再调用Form的print方法将结果输送到打印机,二是将结果直接输送到打印机。如果你采用第一种方式,则无论你怎样调整Form的PrintScal属性,打印出来的东西也不会让你满意。因此建议采用第二种方式。 3.尽量不要使用AssignPrn 尽管AssignPrn简化了文本打印操作,使输出到打印机像输出到文件一样简单。但简单带来的是一系列的不方便:你无法知道当前打印的行数,无法准确控制行距,无法灵活改变字体字形等等。还是用打印机的Canvas属性进行打印吧。 4.用打印机的点数做度量单位 如果想让打印程序在任何打印机上都能正常地打印,你就必须改变你的度量单位。如果采用固定的度量,不同分辨率的打印效果是不同的。举例来讲:printerCanvasrectangle(0,0,360,720)在360×360的佳能4200SP上能打出一个1英寸宽、2英寸高的矩形,但在600×600的惠普6L上只能打出06英寸宽、12英寸高的矩形。使用打印机的点数做为度量单位是一个明智的选择。具体做法如下:VarPointX,PointY:integer;PointX:=GetDeviceCaps(printerHandle,LOGPIXELSX);PointY:=GetDeviceCaps(printerHandle,LOGPIXELSX);printerCanvasrectangle(0,0,PointX*1,PointY*2)这样,无论你使用什么样的打印机,都能得到一个1英寸宽、2英寸高的矩形。 5.添加打印程序单元 尽管Delphi在生成窗体时会自动在USES部分加入许多程序单元,但打印程序单元(Printers)却不在之列,要想使打印机正常工作和程序不出错,你还是老老实实手工给它加上吧。 Delphi编写一个打印程序 如果你想自己用Delphi编写一个打印程序,那么,下面这些技巧或许对你有所帮助。

16

1.获娶显示当前打印机的分辨率 Windows下的打印分辨对打印程序有着至关重要的作用,如果你想知道打印机的分辨率,请在程序中加入一行:ShowMessage(′水平分辨率′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSX))+chr(13)+′垂直分辨率:′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSY)));结果就一目了然了。 2.将结果直接送到打印机 Delphi提供了两种打印方式:一是将结果输送到Form,再调用Form的print方法将结果输送到打印机,二是将结果直接输送到打印机。如果你采用第一种方式,则无论你怎样调整Form的PrintScal属性,打印出来的东西也不会让你满意。因此建议采用第二种方式。 3.尽量不要使用AssignPrn 尽管AssignPrn简化了文本打印操作,使输出到打印机像输出到文件一样简单。但简单带来的是一系列的不方便:你无法知道当前打印的行数,无法准确控制行距,无法灵活改变字体字形等等。还是用打印机的Canvas属性进行打印吧。 4.用打印机的点数做度量单位 如果想让打印程序在任何打印机上都能正常地打印,你就必须改变你的度量单位。如果采用固定的度量,不同分辨率的打印效果是不同的。举例来讲:printerCanvasrectangle(0,0,360,720)在360×360的佳能4200SP上能打出一个1英寸宽、2英寸高的矩形,但在600×600的惠普6L上只能打出06英寸宽、12英寸高的矩形。使用打印机的点数做为度量单位是一个明智的选择。具体做法如下:VarPointX,PointY:integer;PointX:=GetDeviceCaps(printerHandle,LOGPIXELSX);PointY:=GetDeviceCaps(printerHandle,LOGPIXELSX);printerCanvasrectangle(0,0,PointX*1,PointY*2)这样,无论你使用什么样的打印机,都能得到一个1英寸宽、2英寸高的矩形。 5.添加打印程序单元 尽管Delphi在生成窗体时会自动在USES部分加入许多程序单元,但打印程序单元(Printers)却不在之列,要想使打印机正常工作和程序不出错,你还是老老实实手工给它加上吧。 打印功能的实现 对于每一个Windows应用程序开发者来说,打印功能的实现都是极为棘手的,因为要涉及到相当多的代码,而且常常是相当复杂,但是在Delphi中实现文本和图像的打印功能却是非常容易的。 文本的打印功能 Delphi中文本的打印功能就如同在DOS中一样容易,需要做的只是取得打印参数,打开打印机,然后发送文本的每一行内容。在Delphi中提供了一个printers程序单元,它说明了一个tprinter对象,封装了Windows打印工作和输出打印机之间的接口,并提供常用的属性和方法。其中画布canvas是一个非常有用的属性,它代表了当前打印文件的表面,是以图形方式来工作的,整个打印输出工作仅仅是将打印的内容输出到tprinter的属性canvas上,当全部输出工作完成以后,打印对象(tprinter)把canvas的属性值送到打印机上去。 下面举例来说明如何通过Delphi实现文本内容的打印。在Delphi中提供了printdialog 、printersetupdialog两个控件允许我们进行打印机以及其他影响打印输出的选择,此外最重要的一点是要想实现打印功能必须在编译程序以前将printers加入到interface或者impl ementation的UESE语句当中,因为printer单元包括assignprn和其他控制打印机的过程。 首先在From当中加入memo、printdialog、printersetupdialog和两个BUTTON控件,两个 BUTTON的CAPTION分别为\打印设置\和\打印\。然后编写BUTTON的事件驱动程序。本例比较简单,只要单击\打印\按钮时便可以在打印机上输出文件0S2.TXT。代码如下: implementation uses printers; {$R *.DFM} procedure TForm1.BitBtn1Click(Sender: TObject); begin printersetupdialog1.execute; //选择输出的打印机以及其他打印控制选项 end; procedure TForm1.BitBtn2Click(Sender: TObject); var lines:integer; prntext:system.text; //将PRNTEXT声明为一个在SYSTEM程序单元当中定义的文本文件 begin if printdialog1.execute then assignprn(prntext); //将PRNTEST分配给打印机 rewrite(prntext); //调用REWRITE函数,为输出打开已分配的文件 printer.canvas.font:=memo1.font; //把当前MEMO1的字体指定给打印对象的CANVAS的字体属性 for lines:=0 to memo1.lines.count-1 do

17

writeln(prntext,memo1.lines[lines]); //把MEMO的内容写到打印机对象 system.close(prntext); //关闭打印文件 end; procedure TForm1.FormCreate(Sender: TObject); begin memo1.lines.loadfromfile(‘c:\\dos\\os2.txt’); //在FORM建立时读入C:\\DOS\\S2.TXT文件 end; end. 图形的打印功能 简单的图形打印功能也如打印文本一样容易,只是告诉打印机对象(tprinter)开始打印 ,把图形复制到打印机上去,最后告诉打印机结束打印工作。 举例说明:将上面的例子中的memo控件换成image控件,再经过一些简单修改,图形打印的代码如下: procedure TForm1.BitBtn1Click(Sender: TObject); begin if printdialog1.execute then begin printer.begindoc; printer.canvas.draw(0,0,image1.picture.graphic); printer.enddoc; end; end; 在这种情况下,使用的是打印机的分辨率,图形在页面的左上角开始打印输出,打出的图形很小,通常不能符合要求。但是利用打印机画布canvas的stretchdraw方法,我们可以对图形进行灵活处理,canvas的stretchdraw方法声明为: procedure StretchDraw(const Rect: TRect; Graphic: TGraphic); 其中的Rect参数代表图形输出区域的大小,Trect的类型声明为: TRect = record case Integer of 0: (Left, Top, Right, Bottom: Integer); 1: (TopLeft, BottomRight: TPoint);end; 因此我们只要调整RECT的大小及其在打印页面上的位置,进而达到满意的效果,下面不断放大图形,充满我们定义的矩形区域,并将其定位在打印机画布(canvas)的中央然后输出。代码如下: procedure TForm1.Button1Click(Sender: TObject); VAR strect:Trect; //定义打印输出矩形框的大小 temhi,temwd:integer; begin if printdialog1.execute then begin temhi:=image1.picture.height; temwd:=image1.picture.width; while (temhi<printer.pageheight div 2)and //将图形放大到打印页面的1/2 (temwd<printer.pagewidth div 2) do begin temhi:=temhi+temhi; temwd:=temwd+temwd; end; with strect do //定义图形在页面上的中心位置输出 begin left:=(printer.pagewidth -temwd) div 2; top:=(printer.pageheight-temhi) div 2; right:=left+temwd; bottom:=top+temhi; end; with Printer do begin begindoc; //将放大的图形向打印机输出 canvas.stretchdraw(strect,image1.picture.graphic);

18

enddoc; end; end; end; 动态制作报表 在Delphi中,编程人员利用Quick Report控件可以方便地设计和实现静态报表,但对动态报表的制作仍有不便之处。如制作一个列表式报表时,如何动态调节每一列的宽度?由于报表在预览情况下不能修改,只能在预览前将宽度设定好,因此,如何比较方便地预先设定宽度,就成了一个需要解决的问题。 分析问题 首先可想到一种笨办法,就是在确定报表中需要显示的字段后,对每一个字段的宽度赋予一个初始值(比如80),对于需要调整的字段再通过编辑框进行输入修改。这样的确可以达到预期目的,但由于不直观,可能需要多次调整,影响了效率和方便性。 笔者通过实践,利用DBGRID解决了这个问题。由于是制作与数据库相关的报表,其报表记录肯定需要通过一个SQL语句来产生,那么这些记录就可以先显示在一个DBGRID中。由于DBGRID的各列宽度可以动态调节,那么将适当的DBGRID的各列宽度赋给报表中的各列,就一次性达到了我们预期的目的,而且操作非常方便。 设置窗体 建立两个窗体mainform和repform。在mainform上放置edit1、datasource1、query1、dbgrid1、button1(caption为“提取数据”)和button2(caption为“预览报表”),将datasource1.dataset设定为query1,将dbgrid1的datasource设定为datasource1。在repform上放置Quickrep1,并至少将Quickrep1.bands.hascolumnband和Quickrep1.bands.hasdetailband设为true。 编程实现 运行时,在edit1中输入正确的 SQL语句。点击“提取数据”按钮,将相应记录显示在 dbgrid1中。具体的程序代码如下: procedure Tmainform.button1click(sender:TObject); var s:string; begin s:=edit1.text; with query1 do begin close; sql.clear; sql.add(s); open; end; end; 点击“预览报表”按钮,可以看到形成的报表,如果对宽度摆放还有些不满意,可以关闭预览窗口后重新调整。具体的程序代码如下: procedure Tmainform.button2click(sender:TObject); var leftv,i:integer; //leftv为报表起始位置 cheader:Tqrlabel; detailtext:Tqrdbtext; ?? begin leftv:=0; for i:=0 to dbgrid1.columns.Count-1 do begin cheader:=TQrlabel.Create(repform); with repform.cheader do begin parent:=repform.columnheaderband1; caption:=dbgrid1.columns[i].fieldname; width:=dbgrid1.columns[i].width; height:=repform.columnheaderband1.height; left:=leftv+2; top:=5; end; ??//在leftv 处画表格竖线 leftv:=dbgrid1.columns[i].width+leftv; end; ??//画表头栏边框。起始位为0,总宽度为leftv,也就是dbgrid1的各列宽度之和

19

leftv:=0; //显示记录内容 for i:=0 to dbgrid1.columns.Count-1 do begin detailtext:=TQrdbtext.create(repform); with repform.detailtext do begin parent:=repform.detailband1; dataset:=query1; datafield:=dbgrid1.columns[i].fieldname; width:=dbgrid1.columns[i].width; height:=repform.detailband1.height; left:=leftv+2; top:=5; end; ??//在leftv 处画表格竖线 leftv:=dbgrid1.columns[i].width+leftv; end; ??// 画明晰栏边框。 repform.quickrep1.preview();//报表预览 end; 多栏打印 在所有有关Delphi的书籍中,都没有讲解如何实现一个打印项的多栏打印,而实现生活中往往有许多报表要求同一项目多栏打印,QuickReport提供了报表打印一整套完整的解决方案,完全可以实现同一项目的多栏打印,技术关键是如何控制表中记录提取个数,如果在Treport的DataSet属性中选定一个Table,那么QuickReport每次打印详细列表(BandType=rbDetail)属性的TQRBand时,系统会自动取出一个记录供打印,并将表中的记录指针移到下一位,这样程序无法控制表中记录指针的移动,就无法实现多栏打印,只要程序不在Treport的DataSet属性选定任何表, QuickReport便不会控制表记录的提取,QuickReport特为其它类型数据打印提供了一个OnNeedData事件,QuickReport每次打印详细列表(BandType=rbDetail)属性的TQRBand时,会触发这个事件,要求提供打印数据,如果程序在OnNeedData事件中编写运用表中数据提供打印资料,便能实现多栏打印,现举例说明: ---- 现在要实现一个Table1中Name项两栏打印,在窗口上放TquickReport组件 quickReport1,并在上面放Tband组件band1,将其BandType属性改为Detail,在Tband 组件上并排放两个QRLabel组件QRLabel1和QRLabel2,在quickReport1的OnNeedData事件中写如下代码: procedure TForm1.QuickRep1NeedData(Sender: TObject; var MoreData: Boolean); var I: integer; begin MoreData := True; for I:= 1 to 2 do begin if Table1.Eof then Break; case I of 1: QRLabel1.Caption := Table1.FieldByName('Name').AsString; 2: QRLabel2.Caption := Table1.FieldByName('Name').AsString; end; Table1.Next; end; if Table1.Eof then MoreData := False; end; 为了保证打印或者打印预览开始时Table1指针在第一条记录,在quickReport1的BeforePrint事件中写如下代码: procedure TForm1.QuickRep1BeforePrint(Sender: TCustomQuickRep; var PrintReport: Boolean); begin Table1.First; end; 这样程序调用QuickRep1.Preview或者QuickRep1.Print便能预览或者打印到一个关于Name的从左至右,从上到下的多栏报表。 检测存在打印机 Printers是专门用来控制打印机的,可是在没有安装打印机时,却会提示I/O错误,所以必须有一个检测是否存在打印

20

机的方法,我试过很多方法,可是I/O错误总是比我的判断早出现,所以采用以下的烂招来检测打印机。首先在uses增加Printers,再准备一个列表框ComboBox1,其属性Visible设为FALSE,然后在打印之前执行下列语句,那么就可以检测到是否存在打印机了: procedure TForm1.ButtonClick(Sender: Tobject); begin ComboBox1.Clear; ComboBox1.Items.Assign(Printer.Printers); if ComboBox1.Items.CommaText=``then Messagedlg(`你需要安装打印机才能打印!`,mtError,[mbOk],0); else Form1.Print; end; 精确打印输出的程序实现 简介: 一、概述 在银行,税务,邮政等行业的实际工作中,经常涉及到在印刷好具有固定格式的汇款单,储蓄凭证,税票等单据上的确定位置打印输出相关的信息。在此类需求中,精确地定位单据并打印相关信息,是解决问题]的关键。一般情况下,开发者都是通过在打印机上通过重复的测试来达到实际需求。那么,有没有简单有效而又灵活的方法实现上述功能呢? 二、基本思路 分析上述单据的特征,可以发现:此类打印输出的信息一般比较简短,不涉及到文字过长的折行处理,另外,其打印输出的位置相对固定。因此,我们可以通过用尺子以毫米为单位,测量好每个输出信息位置的横向和纵向坐标,作为信息输出的位置。但由于不同打印机在实际输出效果上,总是存在理论和实际位置的偏差,因此,要求程序具有一定的灵活性,供最终用户根据需要,进行必要的位置调整。因此,可设置一打印配置文件,用于存储横坐标和纵坐标的偏移量,用于用户进行位置校正,从而提供了一定的灵活性。 三、精确打印输出的程序实现 1. 在Delphi中新建一个名为mprint.pas的单元文件并编写如下程序,单元引用中加入Printers略: //取得字符的高度 function CharHeight: Word; var Metrics: TTextMetric; begin GetTextMetrics(Printer.Canvas.Handle, Metrics); Result := Metrics.tmHeight; end; file://取得字符的平均宽度 function AvgCharWidth: Word; var Metrics: TTextMetric; begin GetTextMetrics(Printer.Canvas.Handle, Metrics); Result := Metrics.tmAveCharWidth; end; file://取得纸张的物理尺寸---单位:点 function GetPhicalPaper: TPoint; var PageSize : TPoint; begin file://PageSize.X; 纸张物理宽度-单位:点 file://PageSize.Y; 纸张物理高度-单位:点 Escape(Printer.Handle, GETPHYSPAGESIZE, 0,nil,@PageSize); Result := PageSize; end; file://2.取得纸张的逻辑宽度--可打印区域 file://取得纸张的逻辑尺寸 function PaperLogicSize: TPoint;

21

var APoint: TPoint; begin APoint.X := Printer.PageWidth; APoint.Y := Printer.PageHeight; Result := APoint; end; file://纸张水平对垂直方向的纵横比例 function HVLogincRatio: Extended; var AP: TPoint; begin Ap := PaperLogicSize; Result := Ap.y/Ap.X; end; file://取得纸张的横向偏移量-单位:点 function GetOffSetX: Integer; begin Result := GetDeviceCaps(Printer.Handle, PhysicalOffSetX); end; file://取得纸张的纵向偏移量-单位:点 function GetOffSetY: Integer; begin Result := GetDeviceCaps(Printer.Handle, PhysicalOffSetY); end; file://毫米单位转换为英寸单位 function MmToInch(Length: Extended): Extended; begin Result := Length/25.4; end; file://英寸单位转换为毫米单位 function InchToMm(Length: Extended): Extended; begin Result := Length*25.4; end; file://取得水平方向每英寸打印机的点数 function HPointsPerInch: Integer; begin Result := GetDeviceCaps(Printer.Handle, LOGPIXELSX); end; file://取得纵向方向每英寸打印机的光栅数 function VPointsPerInch: Integer; begin Result := GetDeviceCaps(Printer.Handle, LOGPIXELSY) end; file://横向点单位转换为毫米单位 function XPointToMm(Pos: Integer): Extended; begin Result := Pos*25.4/HPointsPerInch; end; file://纵向点单位转换为毫米单位 function YPointToMm(Pos: Integer): Extended; begin Result := Pos*25.4/VPointsPerInch; end; file://设置纸张高度-单位:mm procedure SetPaperHeight(Value:integer); var Device : array[0..255] of char; Driver : array[0..255] of char; Port : array[0..255] of char;

22

hDMode : THandle; PDMode : PDEVMODE; begin file://自定义纸张最小高度127mm if Value < 127 then Value := 127; file://自定义纸张最大高度432mm if Value > 432 then Value := 432; Printer.PrinterIndex := Printer.PrinterIndex; Printer.GetPrinter(Device, Driver, Port, hDMode); if hDMode <> 0 then begin pDMode := GlobalLock(hDMode); if pDMode <> nil then begin pDMode^.dmFields := pDMode^.dmFields or DM_PAPERSIZE or DM_PAPERLENGTH; pDMode^.dmPaperSize := DMPAPER_USER; pDMode^.dmPaperLength := Value * 10; pDMode^.dmFields := pDMode^.dmFields or DMBIN_MANUAL; pDMode^.dmDefaultSource := DMBIN_MANUAL; GlobalUnlock(hDMode); end; end; Printer.PrinterInde Delphi应用程序中中国式报表的制作 在众多可视化数据库开发工具中,Delphi以其真正的面向对象、高效率、支持多层结构应用开发、支持多层B/S结构开发等优良特性脱颖而出,成为广大编程人员的首选开发工具。 在数据库应用程序开发中,系统设计员、程序设计员需要考虑的一个重要问题是如何设计和输出报表,在Delphi中我们可以采用多种方案来解决这一问题,如运用OLE自动化技术将数据输出到MS-WORD、MS-EXCEL中等,但其中最直接、最本地化的还是使用Delphi3.0/40中的QuickReport报表组件,它是挪威QuSoft公司专门为Delphi 编写的,使用QuickReport可以迅速设计出符合西方人习惯的报表。 然而,在设计中国式报表时,笔者发现在QuickReport中设计列与列之间的竖线和斜线比较困难;虽然QuickReport提供了TQShape控件,使用该控件可以画出列与列之间的竖线,但如果用户不能正确调整TQShape实例的高度,输出报表中的竖线不是不连续就是超长,另外如果我们调整了某个Band的高度,我们将不得不调整该Band下的所有TQShape实例的高度;至于斜线,QuickReport报表组件根本就没有提供这一功能。 笔者认真查找了有关资料,成功地解决以上问题,并愿意将解决方法与大家共享,希望能对大家有所帮助。 1、 解决思路 以TQShape为父类,建立新的控件,新控件可以画竖线、斜线和反斜线。重载TQShape类的Paint方法,这样在设计阶段可以非常直观地画斜线、反斜线和竖线,用户可以在设计阶段选择线的类型,如果选择直线,控件自动将其高度调整为所属Band的高度,用户可以调整其横向位置但不能调整其高度;如果选择斜线,用户可以根据需要调整斜线的长度和倾角。重载TQShape类的Print方法,这样可以在运行阶段输出直线和斜线。 说明:该控件只能画直线和斜线,如果读者需要画矩形和园,可以使用TQShape控件来实现。 2、控件设计步骤 步骤1、使用Delphi提供的控件向导,选择TQShape为父类,建立新类TMyQRShape,并选择适当的包(Package),最后生成单元文件。 步骤2、在生成的单元文件中,增加枚举类型, Tlines = ( None,TopBottom,BottomTop ) ; None、TopBottom、BottomTop三种取值,分别代表直线、斜线 和反斜线 / 。 步骤3、在新类TMyQRShape 中增加private 成员 FLineType:Tlines ,增加published属性 LineType:Tlines Read FLineType Write SetFLineType 。 步骤4、建立过程SetFLineType 。 procedure TMyQRShape.SetFLineType(Value:Tlines); begin if Value<>FLineType then begin FLineType:=Value ; Invalidate ;

23

end ; end ; 步骤5、重载Paint方法 procedure TMyQRShape.Paint ; begin case LineType of BottomTop: begin Canvas.MoveTo(0,Height) ; Canvas.LineTo(width,0 ) ; end ; TopBottom: begin Canvas.MoveTo(0,0) ; Canvas.LineTo(width,Height ) ; end ; None: begin Height := Parent.Height ; Top:=0 ; Width:=4 ; Shape:=qrsVertLine ; Inherited Paint ; end ; end ; end ; 步骤6、重载Print方法 procedure TMyQRShape.Print(OfsX,OfsY : Integer); begin with QRPrinter do begin case LineType of BottomTop: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)+Height) Canvas.LineTo(Xpos(OfsX + Size.Left)+width,Ypos(OfsY + Size.Top) ) ; end ; TopBottom: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)) ; Canvas.LineTo(Xpos(OfsX + Size.Left)+Width,Ypos(OfsY + Size.Top)+Height ) ; end ; None: Inherited Print(OfsX,OfsY ) ; end ; end ; end; 步骤7、保存并安装TMyQRShape控件。 本控件在Delphi40下调试、安装,并成功应用于某数据库管理系统的开发。该控件的完整代码如下。 源程序: unit MyQRShape; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, QuickRpt, Qrctrls; type Tlines = ( None,TopBottom,BottomTop ) ; TMyQRShape = class(TQRShape) private FLineType:Tlines ; procedure SetFLineType(Value:Tlines) ; protected procedure Print(OfsX, OfsY : integer); override; procedure Paint ;Override ;

24

public published property LineType:Tlines Read FLineType Write SetFLineType ; end; procedure Register; implementation procedure TMyQRShape.SetFLineType(Value:Tlines); begin if Value<>FLineType then begin FLineType:=Value ; Invalidate ; end ; end ; procedure TMyQRShape.Paint ; begin case LineType of BottomTop: begin Canvas.MoveTo(0,Height) ; Canvas.LineTo(width,0 ) ; end ; TopBottom: begin Canvas.MoveTo(0,0) ; Canvas.LineTo(width,Height ) ; end ; None: begin Height := Parent.Height ; Top:=0 ; Width:=4 ; Shape:=qrsVertLine ; Inherited Paint ; end ; end ; end ; procedure TMyQRShape.Print(OfsX,OfsY : Integer); begin with QRPrinter do begin case LineType of BottomTop: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)+Height) Canvas.LineTo(Xpos(OfsX + Size.Left)+width,Ypos(OfsY + Size.Top) ) ; end ; TopBottom: begin Canvas.MoveTo(Xpos(OfsX + Size.Left), Ypos(OfsY + Size.Top)) ; Canvas.LineTo(Xpos(OfsX + Size.Left)+Width,Ypos(OfsY + Size.Top)+Height ) ; end ; None: Inherited Print(OfsX,OfsY ) ; end ; end ; end; procedure Register; begin RegisterComponents(`Qreport`, [TMyQRShape]); end;

25

用Delphi编写打印程序的窍门 如果你想自己用Delphi编写一个打印程序,那么,下面这些技巧或许对你有所帮助。 1.获娶显示当前打印机的分辨率 Windows下的打印分辨对打印程序有着至关重要的作用,如果你想知道打印机的分辨率,请在程序中加入一行:ShowMessage(′水平分辨率′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSX))+chr(13)+′垂直分辨率:′+inttostr(GetDeviceCaps(printerHandle,LOGPIXELSY)));结果就一目了然了。 2.将结果直接送到打印机 Delphi提供了两种打印方式:一是将结果输送到Form,再调用Form的print方法将结果输送到打印机,二是将结果直接输送到打印机。如果你采用第一种方式,则无论你怎样调整Form的PrintScal属性,打印出来的东西也不会让你满意。因此建议采用第二种方式。 .尽量不要使用AssignPrn 管AssignPrn简化了文本打印操作,使输出到打印机像输出到文件一样简单。但简单带来的是一系列的不方便:你无法知道当前打印的行数,无法准确控制行距,无法灵活改变字体字形等等。还是用打印机的Canvas属性进行打印吧。 4.用打印机的点数做度量单位 如果想让打印程序在任何打印机上都能正常地打印,你就必须改变你的度量单位。如果采用固定的度量,不同分辨率的打印效果是不同的。举例来讲:printerCanvasrectangle(0,0,360,720)在360×360的佳能4200SP上能打出一个1英寸宽、2英寸高的矩形,但在600×600的惠普6L上只能打出06英寸宽、12英寸高的矩形。使用打印机的点数做为度量单位是一个明智的选择。具体做法如下:VarPointX,PointY:integer;PointX:=GetDeviceCaps(printerHandle,LOGPIXELSX);PointY:=GetDeviceCaps(printerHandle,LOGPIXELSX);printerCanvasrectangle(0,0,PointX*1,PointY*2)这样,无论你使用什么样的打印机,都能得到一个1英寸宽、2英寸高的矩形。 5.添加打印程序单元 尽管Delphi在生成窗体时会自动在USES部分加入许多程序单元,但打印程序单元(Printers)却不在之列,要想使打印机正常工作和程序不出错,你还是老老实实手工给它加上吧。 用Delphi实现打印功能 给单位开发软件,涉及一打印模块,我感到颇有兴趣,就拿来其中的一个小功能模块与读者共享。下面以打印在纸张的矩形框内为例简单介绍: 程序要求: 单击[打印]按钮,把Memo的内容最多分三行打印出来,每行最多能容纳22个三号字,限定汉字上限为50个汉字。 编程思路: 用LineTo和MoveTo函数画一矩形框,根据Memo组件的内容长度用Copy函数把它分割为1到3个子串。在矩形框内美观地输出文字时技术处理为:当输出一行时最多可打印18个汉字,当输出多行时第一、二行分别打印16、18个汉字。 编程步骤: 1、首先新建一工程,在窗体上加一个Memo组件Button组件。 2、Memo组件的Lines值为空,MaxLength值为“100”(即50个汉字),字体为“三号字”;Button的Caption值为“打印”。 3、添加[打印]按钮的事件处理过程代码Button1.Click,首先在Interface的Uses部分添加Printers,其完整代码如下: procedure TForm1.Button1Click(Sender: TObject); var StrLen , Left,Top , WordHeight , wordWidth : Integer; ContentStr : String[100]; Str1, Str2, Str3 : String[36]; begin with Printer do begin Canvas.Font.Size:=16; wordHeight:=Canvas.TextHeight ('字'); wordWidth:=Canvas.TextWidth ('字'); Left:=(Printer.PageWidth-wordWidth*22) div 2; Top:=(Printer.PageHeight-wordHeight*7) div 2; BeginDOC; With Canvas do begin Pen.Width:=3; {画一个22字宽,7个字高的矩形框} MoveTo(Left,Top); LineTo(Left+wordWidth*22,Top); LineTo(Left+wordWidth*22,

26

Top+wordHeight*7); LineTo(Left,Top+wordHeight*7); LineTo(Left,Top); ContentStr:=Memo1.Lines.Text; StrLen:=Length(ContentStr); if StrLen< 37 then {分一行打印} begin TextOut(Left+WordWidth*2, Top+Wordheight*3, ContentStr) end else if StrLen< 66 then {在垂直方向中间分两行打印} begin Str1:=Copy(ContentStr, 0, 32); Str2:=Copy(ContentStr, 33, StrLen-32); TextOut(Left+WordWidth*4, Top+WordHeight*(7-2) div 2 , Str1); TextOut(Left+WordWidth*2, Top+WordHeight*(7-2) div 2 + wordHeight, Str2); end else {分三行打印} begin Str1:=Copy(ContentStr,0,32); Str2:=Copy(ContentStr,33,36); Str3:=Copy(ContentStr, 69, StrLen-68); TextOut(Left+WordWidth*4, Top+WordHeight*2, Str1); {左缩进两个汉字} TextOut(Left+WordWidth*2, Top+WordHeight*3, Str2); TextOut(Left+WordWidth*2, Top+WordHeight*4, Str3); end end; EndDoc; end; end; 以上程序在Windows 98/Me+Delphi 6.0调试通过,希望能对初次编写打印功能程序的读者有所帮助。 网络远程报表 目前,阜新市国有大中型企业生产经营监测网已正式投入使用,网络覆盖市经委及全市国有大中型企业,为改变传统的企业报表处理方式,提高机关工作效率,及时、准确反映全市工业企业的生产经营状况,我们设计开发了网络远程报表软件。 1、软件应用的网络环境 ---- 1.1、网络模型 < 此处略去图> 我们采用B/S模式来设计网络远程报表软件。在此模式中,Web服务器实现整个业务处理逻辑,而客户端只提供人机交互界面,这种模式的最大优势可以概括为二点,一是集中化的事物处理逻辑,二是瘦客户机,因此我们可以用较少的资源建立起具有很强伸缩性、且易升级维护的应用系统。 ---- 1.2、系统软件 ---- Web服务器使用的软件有Windows NT Server 4.0 、Microsoft Information Server 3.0、Microsoft SQL Server 7.0 Clients、Borland的数据库引挚。数据库服务器安装的软件有Windows NT Server 4.0、Microsoft SQL Server 7.0。所有客户机全部采用Windows 95或Windows 98操作系统。 2 使用的程序设计工具 Borland公司在开发工具方面一直居于领先地位,特别表现它基于BDE的开放数据库连接技术及可视化快速应用程序开发工具等方面。它的Delphi4不仅是一种高效的开发工具,更提供了面向分布式对象及Web应用的新技术,因此我们选择Delphi4做为应用程序开发工具。 Delphi4提供ISAPI技术用于创建Web交互程序,ISAPI是Microsoft提出的技术标准,用于扩展Microsoft Information Server的功能。 ---- 使用Delphi4建立的ISAPI程序结构如下: < 此处略去图> 结构的核心是Web模块,它是一TWebModule类型的构件,其主要作用有两个,一是负责管理程序使用的非可视化构件及请求处理器,二是充当客户HTTP请求分配器,把客户的请求交给相应的请求处理器。请求处理器能读取客户提交的请求信息,进行相应处理,并产生Html形式的回应信息。内容生成器用于产生Html形式的内容。 ---- Web应用程序工作流程:

27

---- 客户提交Http请求信息给Web服务器,Web服务器根据请求信息类型,把这一Http请求交给相应的Web应用程序。 Web应用程序接收Http请求信息,它建一TWebQuest对象来表示客户请求内容,同时建一TWebResponse对象以存贮回应信息,然后把这两个对象交给Web模块。 ---- Web模块根据TWebquest的内容,调用相应的请求处理器来处理这一请求。 请求处理器读取TWebquest的信息进行处理,根据具体情况,有时请求处理器需借助内容生成器来产生回应内容,有时直接产生回应内容,然后把回应内容放入TWebResponse对象,再回传给Web模块。 ---- 如果这一请求没有处理完,Web模块再选择其它请求处理器来处理,否则,Web模块把回应信息交给Web应用程序。 --- Web应用程序把回应信息交给Web服务器,Web服务器再转交给客户。 3 设计网络报表软件 3.1 定义数据库结构 ...... < 此处略去> 所有企业上报的数据按月分别存储在一个表中,表名为ZB[当前报告期]。每个经济指标在ZB表中有4个字段与之对应,它们分别是Tl### 、Ql###、T###、Q###,其中###为经济指标代码。 ---- 3.2、建立Web应用项目 ----在Delphi4中,选择\,从弹出的窗口中,选中Web Server Applicapion,这样就创建一个ISAPI程序框架。 ---- 3.3、配置非可视化部件 < 此处略去图,>CustomerInfoModule: ---- 它是一TcustomerInfoModule类型模块,其属性定义:Name= CustomerInfoModule;其它属性使用隐含的配置。---- OnCreate事件处理程序: procedure TCustomerInfoModule. CustomerInfoModuleCreate(Sender: TObject); begin Session.Active:=true; Database.Connected:=true; Tcqy.Active:=true; Tzbiao.Active:=true; Tztime.Active:=true; Tzb.TableName:='dbo.zb'+tztime.Fieldvalues['tend']; Tzb.Active:=true; end; ---- OnDestroy事件处理程序: procedure TCustomerInfoModule. CustomerInfoModuleDestroy(Sender: TObject); begin Session.Active:=false; end; ---- Session构件: ---- Session是一Tsession类型的构件,它是连接数据库的上下文。由于ISAPI是多线程应用程序,每个线程都使用独立的上下文来连接数据库,因此需要显示定义Tsession构件,不能使用隐含定义的Tsession构件。 ---- Session构件的属性:Active=False;AutoSessionName=True;Name=Session;其它属性使用隐含的配置。 ---- Database构件: ---- Database是一Tdatabase类型构件,它用来管理数据库连接,使用自定义的Database构件,可以对数据库连接进行控制。 ---- Database 构件的属性:AliasName=qydata;Connected=False;DatabaseName=Company; LoginPrompt=False;Params={ Username=xk;Password=xk};Name=Database;其它属性使用隐含配置。 ---- Tcqy构件: ---- Tcqy为一TTable类型构件,它提供对数据库表cqy访问。 ---- Tcqy构件属性:Active=False;DatabaseName=Company;Name=Tcqy; Tablename=dbo.cqy;其它属性使用隐含配置。 ---- Tzbiao、Tztime、Tzb同为TTable类型构件,属性设置与Tcqy构件相似,它们分别与数据表zbiao、ztime、zb相对应。 ---- Page构件: ---- Page为一TPageProducer类型的构件,利用此构件可产生Html形式的内容。 ---- Page构件的属性: Name=Page HTMDoc的内容: < html > < /head > < body > ,,, < #title > //标签的内容由OnHTMTag事件处理 ,,, < /body > < /html > ---- 其它属性使用隐含配置。 ---- Page构件的OnHTMLTag事件处理程序: procedure TCustomerInfoModule.PageHTMLTag(Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin ReplaceText:=''; Tzbiao.first; while not Tzbiao.eof do begin ReplaceText:=ReplaceText+'< tr >'; ReplaceText:=ReplaceText+' < td width=\+Tzbiao.Fieldvalues['name']+'< /td >'; ReplaceText:=ReplaceText+' < td width=\Tzbiao.Fieldvalues['dan']+'< /td>'; ReplaceText:=ReplaceText+'< td width=\

28

size=\ReplaceText:=ReplaceText+ '< td width=\< input type=\name='+'Q'+TzbiaoFieldvalues['dm']+' size=\maxlength=\ReplaceText:=ReplaceText+'< /tr >'; Tzbiao.next; end; end; ---- 3.4、定义请求处理器 ---- 网络报表软件总共需要二个请求处理器,下面分别介绍它们的功能及配置。 ---- Control处理器 ---- 它用来产生输入报表的HTTP页面。 ---- Control的属性:Default=False;Enabled=True;methodTtpe=mtPost;Name=control;PathInfo =/control. ---- Control的OnAction事件处理程序: procedure TCustomerInfoModule.CustomerInfoModulecontrolAction( Sender: TObject; Request: TWebRequest;Response: TWebResponse; var Handled: Boolean);beginResponse.content:=page.content;Response.SendResponse;end; ---- 产生的回应内容显示如下: < 此处略去图,全文可下载 > ---- Append处理器 ---- 它用来把报表数据追加到数据库表中。 ---- Append 的属性:Default=False;Enabled=True;MethodTtpe=mtPost;Name=append;PathInfo =/append。 ---- Append 的OnAction事件处理程序: < 此处略去> ---- 3.5 访问SQL Server 7.0的方法 ---- 我们使用ODBC界面实现对SQL Server 7.0的访问。ODBC是一个用于访问数据库的统一界面,它具有良好的数据库独立性,通过ODBC可以访问各种类型的数据库系统。 ---- 在配置ODBC时,选择\系统 DSN\方式管理数据源,这样的数据源属于本地计算机,而不属于具体用户,所以Web服务器的匿名帐号就能识别SQLServer7.0的数据源。 ---- 3.6 安全处理 ---- 利用法人代码与操作密码存在的一一对应关系,来控制用户对后台应用程序的调用。 由于所有报表用户都有相同的数据库操作权限,所以采用所有用户共享一个SQL server帐号来实现对数据库表的访问。 4 安装应用程序 由于WWW的匿名帐号有权运行Scripts目录下的程序,所以把网络报表程序放入WWW的子目录Scripts中,网络用户就能通过游览器访问后台的应用程序。 ---- [本文为节略] 中国式报表 在众多可视化数据库开发工具中, Delphi以其真正的面向对象、高效率、支持多层结构应用开发、支持多层B/S结构开发等优良特性脱颖而出,成为广大编程人员的首选开发工具。 ---- 在数据库应用程序开发中,系统设计员、程序设计员需要考虑的一个重要问题是如何设计和输出报表,在Delphi中我们可以采用多种方案来解决这一问题,如运用OLE自动化技术将数据输出到MS-WORD、MS-EXCEL中等,但其中最直接、最本地化的还是使用Delphi3.0/40中的QuickReport报表组件,它是挪威QuSoft公司专门为Delphi 编写的,使用QuickReport可以迅速设计出符合西方人习惯的报表。 ---- 然而,在设计中国式报表时,笔者发现在QuickReport中设计列与列之间的竖线和斜线比较困难;虽然QuickReport提供了TQShape控件,使用该控件可以画出列与列之间的竖线,但如果用户不能正确调整TQShape实例的高度,输出报表中的竖线不是不连续就是超长,另外如果我们调整了某个Band的高度,我们将不得不调整该Band下的所有TQShape实例的高度;至于斜线,QuickReport报表组件根本就没有提供这一功能。 ---- 笔者认真查找了有关资料,成功地解决以上问题,并愿意将解决方法与大家共享,希望能对大家有所帮助。 ---- 1、 解决思路 ---- 以TQShape为父类,建立新的控件,新控件可以画竖线、斜线和反斜线。

29

---- 重载TQShape 类的Paint方法,这样在设计阶段可以非常直观地画斜线、反斜线和竖线,用户可以在设计阶段选择线的类型,如果选择直线,控件自动将其高度调整为所属Band的高度,用户可以调整其横向位置但不能调整其高度;如果选择斜线,用户可以根据需要调整斜线的长度和倾角。 ---- 重载TQShape 类的Print方法,这样可以在运行阶段输出直线和斜线。 ---- 说明:该控件只能画直线和斜线,如果读者需要画矩形和园,可以使用TQShape控件来实现。 ---- 2、控件设计步骤 ---- 步骤1、使用Delphi提供的控件向导,选择TQShape为父类,建立新类TMyQRShape,并选择适当的包(Package),最后生成单元文件。 ---- 步骤2、在生成的单元文件中,增加枚举类型, TLines = ( None,TopBottom,BottomTop ) ;None、TopBottom、BottomTop三种取值,分别代表直线、斜线 \\ 和反斜线 / 。 ---- 步骤3、在新类TMyQRShape 中增加private 成员 FLineType:TLines ,增加published属性 LineType:TLines Read FLineType Write SetFLineType 。 ---- 步骤4、建立过程SetFLineType 。 ... ... ---- 步骤5、重载Paint方法 ... ... ---- 步骤6、重载Print方法 ... ... ---- 步骤7、保存并安装TMyQRShape控件。

30

本文来源:https://www.bwwdw.com/article/329g.html

Top