canvas教程

Delphi报表制作技巧20篇

字号+ 作者:H5之家 来源:H5之家 2015-11-13 17:26 我要评论( )

Delphi 报表制作技巧 目 录Delphi 3 中报表的制作........................................................................................................

Delphi报表制作技巧20篇


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 + end ; TopBottom: begin Canvas.MoveTo(Xpos(OfsX + Canvas.LineTo(Xpos(OfsX + Size.Top)+Height ) ; end ; None: Inherited Print(OfsX,OfsY end ; end ; end; Size.Left)+width,Ypos(OfsY + Size.Top) ) ;

Size.Left), Ypos(OfsY + Size.Top)) ; Size.Left)+Width,Ypos(OfsY +

) ;

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)+ end; procedure TMyPreview.ToolButton2Click(Sender: TObject); begin //ZoomToFit 缩 放 至 全 屏 QRPreview1.ZoomToFit; end; procedure TMyPreview.ToolButton3Click(Sender: TObject); begin //ZoomTo100% QRPreview1.Zoom:=100; end; 缩 放 至 实 际 大 小

' 页

总 '+inttostr(FPageCount)+' 页';

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)); 3: CurRep.ExportToFilter(TQRHTMLExportFilter.Create(SaveDlg1.FileName)); 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 0: DefaultExt := 1: DefaultExt := 2: DefaultExt := 3: DefaultExt := end; end; end.//end of Unit

//--*.HTM //--*.CSV

of '.QR'; '.TXT'; '.HTM'; '.CSV';

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"年"m"月"d"日"',Date); 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.5 4*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&lt; 37 then {分一行打印} begin TextOut(Left+WordWidth*2, Top+Wordheight*3, ContentStr) end else if StrLen&lt; 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 中,选择"File->New Applicapion",从弹出的窗口中,选中 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="180" height="30" >' +Tzbiao.Fieldvalues['name']+'< /td >'; ReplaceText:=ReplaceText+' < td width="76" height="30" >'+ Tzbiao.Fieldvalues['dan']+'< /td>'; ReplaceText:=ReplaceText+'< td width="143" height="30" >< input type="text" name='+'T'+Tzbiao.Fieldvalues['dm']+'

28

size="12"maxlength="12" < /td >'; ReplaceText:=ReplaceText+ '< td width="186" height="30" > < input type="text" name='+'Q'+TzbiaoFieldvalues['dm']+' size="12" maxlength="12" < /td >'; 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


 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • TP15 Canvas画图

    TP15 Canvas画图

    2016-12-03 11:04

  • Tkinter教程之Canvas篇(3)

    Tkinter教程之Canvas篇(3)

    2016-12-02 14:00

  • 相似辅助线作图技巧大比拼(讲义)王闫闫

    相似辅助线作图技巧大比拼(讲义)王闫闫

    2016-09-28 17:00

  • Android—BitMap与Canvas学习笔记

    Android—BitMap与Canvas学习笔记

    2015-11-21 19:17

网友点评