DELPHI编程技巧集锦

<!-- @page { margin: 2cm } H1 { margin-top: 0.6cm; margin-bottom: 0.58cm; line-height: 200%; page-break-inside: avoid } H1.western { font-family: "DejaVu Serif", serif; font-size: 22pt } H1.cjk { font-family: "DejaVu Sans"; font-size: 22pt; font-style: normal; font-weight: bold } H2 { margin-top: 0.46cm; margin-bottom: 0.46cm; line-height: 173%; page-break-inside: avoid } H2.western { font-family: "Arial", sans-serif; font-size: 16pt } H2.cjk { font-family: "黑体"; font-size: 16pt; font-style: normal } H2.ctl { font-family: "文泉驿正黑"; font-size: 12pt; font-weight: normal } H3 { margin-top: 0.46cm; margin-bottom: 0.46cm; line-height: 173%; page-break-inside: avoid } H3.western { font-family: "DejaVu Serif", serif; font-size: 16pt } H3.cjk { font-family: "DejaVu Sans"; font-size: 16pt; font-style: normal } H3.ctl { font-family: "文泉驿正黑"; font-size: 12pt; font-weight: normal } P { margin-bottom: 0.21cm } A:link { color: #0000ff } -->

DELPHI 编程技巧集锦

董占山

( 中国农科院棉花研究所,河南安阳, 455112)

美国著名的《Delphi 开发者杂志(Delphi Developer\'s Journal) 》是世界上众多Delphi 程序员必读的专业杂志,在国内我们很少有机会读到这份优秀的专业刊物,但是我们可以在Inprise 公司(http://www.inprise.com )的网页上看到该杂志刊登的一些优秀文章。同时,还可以通过电子邮件订阅该杂志免费提供的Delphi 使用技巧,订阅网址为http://www.zdtips.com/ddj/bor-f.htm 。笔者从中筛选出一些十分有用的Delphi 使用技巧和文章,编译出来,与广大Delphi 爱好者分享。有什么意见和建议可以直接给笔者发电子邮件(dzs@126.com )。读者请注意,本文中的内容多以Delphi 4 为例进行介绍,请使用其他版本的读者根据实际情况灵活运用。

一、Delphi 集成环境与代码调试

A 修改Delphi 的系统信息

默认的Delphi 消息、警告和例外描述都是英文的,将这些内容翻译成另一种语言或修改它们使其适合你的需要的最简单方法是编辑资源文件并改变相应的字符串,这些文件位于BIN 目录。主要资源文件有:SysUtils 单元的信息(文件没有找到、转换错误信息等)在sysutils.res 中,数据库错误信息在dbconsts.res 中,VCL 信息在consts.res 中。注意一些字符串使用格式字符,如%s 或%d ,来产生最终的字符串,在这种情况下,应将这些字符保留在适当的位置。

B 如何清除无用代码

Delphi 提供了自动清除源代码中无用代码的强大功能,一般来说,当你保存文件时,Delphi 自动将源代码中空的类方法删除,保证了源代码的清洁。在没有编译和保存文件的前提下,也可以清除无用代码,方法是:在Delphi 3/4 中单击“File”*“Save As...” 菜单命令(在Delphi 1/2 中单击“File”*“Save File As...” 菜单命令),打开“Save As...” 对话窗口,单击“取消”按钮即可。

C 在Delphi 4 集成环境中不使用浮动功能

你无疑知道Delphi 4 集成环境支持大多数窗口的浮动功能。但是,有时你不想让一个窗口具有浮动功能。浮动窗口在需要时,十分有用,但在不需要时,也十分恼人。有两种方法可以改变一个窗口的浮动属性。

第一种方法是:每个可浮动窗口具有一个本地菜单项目---Dockable ,如果你不希望一个特定的窗口具有浮动功能,简单地用鼠标右键单击窗口,选空Dockable 菜单项目。这个窗口的浮动功能就关闭了,直到你再选中Dockable 项目为止。

这一技术可以防止特定的窗口可停靠。但有时,你仅仅希望暂时关闭浮动功能,这时,只需要按下<Ctrl> 键,再拖动窗口。

D 在工具菜单中添加项目

在Delphi 集成环境中按F1 键可以打开多数Delphi 帮助标题,这种方法可以快速打开相关标题的帮助窗口。但是这种快速方法对第3方工具和常问问题(FAQs )是不可用的,Delphi 提供了一个变通的方法,使在集成环境下快速打开这些工具成为可能。

在工具菜单添加用户项目的方法是:单击“Tools”*“Configure Tools” 命令,打开一个包含所有可用工具列表的对话窗口,单击“Add” 按钮,打开“Tool Properties” 对话窗口,分别设置4 个编辑框,然后单击“Ok” 按钮,再单击“Close” 按钮,完成设置。

E 设置条件断点

一般来说,大家都会使用断点来调试程序,但是如何使用条件断点来调试程序呢?条件断点,顾名思义,就是指需要满足一定条件时的断点。这种断点在调试很长的For 或While 循环时十分有用,当你只希望看一看一个特定循环的执行情况而非所有循环时,就需要在循环中设定一个条件断点,当设定的条件满足时,Delphi 停止应用程序的执行。

设置条件断点的方法是:按常规的方法建立断点,单击“View”*“Debug Windows”*“Breakpoints” 命令,弹出一个断点列表窗口,用鼠标右键单击欲设置为条件断点的断点,在快捷菜单中单击“Properties” 命令,打开一个断点编辑窗口,在这个窗口的条件域中输入一个逻辑表达式即可。在调试程序时,Delphi 判断这个逻辑表达式,当逻辑表达式为真时,就中断程序运行,返回代码窗口。

F 不要让集成调试器打断调试过程

在调试程序时,Delphi 的集成调试器监视一切运行时错误。当调试器发现一个运行时错误时,Delphi 中断应用程序并返回到设计状态,并显示一个错误信息窗口。当关闭错误信息窗口后,需要按<Ctrl-F2> 重新开始启动程序,或者按<F9> 继续运行程序。无疑,Delphi 集成调试器是十分有用的,但有时也让人烦恼。能否暂时关闭集成调试器呢?可以。使用下面的方法可防止集成调试器中断应用程序:

1 单击“Tools”*“Environment Options...” 菜单命令;

2 单击“Preferences” 对话页标签;

3 选空“Integrated debugging option” 复选框;

4 单击“Ok” 完成操作。

这样当你在集成环境下调试应用程序时,Delphi 的集成调试器探测到运行时错误时,就不再切换到设计状态并显示错误信息了。

G 调试Delphi 3/4 集成环境的插件

在Delphi 1 中, 要调试集成环境的插件/ 专家是十分困难的。Delphi 3/4 提供了调试DLL 的能力,从而简化了这项工作。

第一步,保证插件/ 专家没有包括在Windows 注册表的插件/ 专家列表中,然后,启动Delphi 3/4 并装载需要调试的专家DLL ,修改注册表,使Delphi 3/4 能够调用这个DLL ;

第二步,单击“Run”*“Parameters” 菜单命令,打开“Run Parameters” 对话窗口,单击“Local” 对话页上的“Host Application” 编辑框右边的“Browse” 按钮,查找“Delphi32.exe” 程序的位置(本例为C:/Program Files/Borland/Delphi4/Bin/delphi32.exe );

第三步,运行待调试的DLL ,将启动Delphi 的第二个实例,并装载要调试的DLL ,允许对其进行调试。

二、窗体设计的相关技巧

A 透明象素点

当将一个image 图象,一般为BMP 文件,放到一个TBitBtn 上时,图片左下角的一个象素点决定图片中的哪种颜色为透明色。图片上任何具有这种颜色的象素点,在按钮上都是透明的。假如不希望图片上的任何象素点是透明的,就需要将图片左下角的这个象素点的颜色设置为不同于图片上任何象素点的颜色。

B 自动调整窗体的分辨率

创建应用程序时,总是依监视器的分辨率进行的,其缺点是:假如在较高分辨率下设计应用程序,它可能大于用户的有效屏幕大小,在用户使用程序时,就不能显示出全部窗体内容,给用户带来不便。一种简单的解决办法是:在程序运行时,让Delphi 自动添加滚动条来解决这个问题。

但是,使用Delphi 的自动调整比例过程将产生更加专业的结果。在运行时,Delphi 获得系统的屏幕分辨率,并将结果保存在应用程序的Screen 对象的PixelsPerInch 属性中,然后,使用这个属性的值将窗体调整到当前分辨率。

记住,为了有效地使用这项技术,需要设置窗体的Scaled 属性为真,并且只用TrueType 字体,如果开发程序时,使用了Windows 的小字体,应将窗体的AutoScroll 属性设置为假(FALSE )。

C 为控制设置一种自定义颜色

窗体和各种控制都具有一个Color 属性,当你选择它们的Color 属性时,可以在列表框中选择一种Windows 系统默认的各种颜色,也可以建立一种自定义颜色,使它们显得与众不同。为窗体或控制设定自定义颜色的步骤如下:

1 双击组件的Color 属性,弹出颜色对话窗口;

2 选择一种最接近你想要的基色;

3 单击“Define Custom Colors>>” 按钮,颜色对话窗口将扩展,显示出一个色谱区域;

4 使用十字光标在这个色谱区域选择你想要的颜色,然后单击“Add to Custom Colors” 按钮;这样你选定的特定颜色就被添加到颜色对话窗口中了;

5 单击“Ok” 按钮,就将刚定义的颜色应用到选定的控件了。

D 缩小步长

大多数程序员在设计窗体时喜爱“(靠到格线)Snap to grid” 功能,可以节省安置组件的时间,但是,有时你还需要微调其位置和大小。

其一:将组件在窗体上一次移动一个象素点。首先,选中你想移动的组件,然后,按下<Ctrl> 键不放,按光标键,选中的控制将一次移动一个象素点,方向与光标键所指方向相同。

其二:每次按一个象素点调整控制的大小。选中控制,按下<Shift> 键不放,按光标键,根据光标键所指方向不同,选中的控制每次放大或缩小一个象素点。

E 控制滚动条的有效方法

TForm 的HorzScrollBar 和VertScrollBar 属性使用Tracking 子属性来管理窗体的显示,Tracking 属性是一个布尔型属性。若此属性设置为真,窗体随用户拖动滚动条而移动;若此属性设置为假,窗体不随用户拖动滚动块而移动,只有用户释放滚动块时才移动。这种差别对查看列表和图象的用户十分重要。如果要平滑地显示列表和图象,将Tracking 属性设置为真,但当图象或列表信息特别复杂时,窗口的滚动特别地缓慢。如果要快速显示列表和图象信息,将Tracking 属性设置为假,这样窗口的滚动就会加快,但是由于不能看到实际位置,所以使用时难以掌握。除了TForm 以外,TScrollBox 组件也使用Tracking 属性来管理其显示内容。

F 选择合适的组合框

Delphi 提供了5 类组合框,它们具有相同的特性,但是也有不同的特点。了解其间的差别,可帮助程序员根据需要选择合适的组合框类型。

所有的组合框都是一个列表框和编辑框的组合,用户可以在列表框中选择或在编辑框中直接输入值。当用户从列表框中选择时,这个条目将显示在编辑框中。5 类组合框的不同特性决定了它们的显示和与用户交互的方式。下表列出了5 种类型组合框的的独有特征。

表1 格式描述

格式

说明

Simple

这种格式就是列表框上显示一个编辑框,用户可以从列表框中选取条目,也可以直接在编辑框中输入文本

Drop-down

除了列表框开始不显示外,其他特性均类似于simple 格式。在编辑框的左边有一个下拉按钮,单击可以打开列表框,从中选取条目;也可在编辑框中直接输入项目。

Drop-down list

这是组合框中限制条件最多的一种,显示格式类似于drop-down ,开始时列表框不显示。用户单击下拉按钮打开列表框并从中选取条目,但是不能在编辑框中直接输入文本。

OwnerDrawFixed

这种组合框类似于Simple 类,不同的是其列表框中的条目高度是根据用户在ItemHeight 定义的值而设置的。

OwnerDrawVariable

这种组合框类似于OwnerDrawFixed 类,特点是列表条目的高度是可变的。

当窗体上有足够的空间和列表很短时,使用Simple 格式的组合框较为合适。否则,使用Drop-down 格式的组合框。当想让用户只能从预定义项目中选取条目时,用Drop-down list 格式的组合框。需要可变高度列表项时,使用后两种。

G 使非可视组件易于辨认

非可视组件没有标题属性,一个窗体中多个同类非可视组件时,由于它们看起来一模一样,故难以辨认。DELPHI 提供了一种变通的方法,就是将非可视组件的名称放置在组件图标之下,使它们易于辨认。设置方法如下:

1 单击“Tools”*“Environment Option” ,弹出一个对话窗口;

2 单击“Preferences” 标签,切换到Preferences 对话页;

3 选中“Show component captions” 复选框;

4 单击“Ok” 完成。

这时,在当前的设计窗体上,就可以看到每个非可视组件下显示出一个标签。这个选项设置之后,对所有窗体都是有效的。

H 标签的加速键

对含有Caption 属性的组件,添加快捷键是比较容易的,只需在Caption 属性中特定字符前加上“&” 符号即可。那么,怎样给没有Caption 属性的控制添加快捷键呢?现以给一个TMemo 控制添加快捷键为例说明如下:在窗体上放置一个TMemo 控制,再在其旁边放置一个TLabel 控制,将其Caption 属性设置为“&Memo1” ,将TLabel 的FocusControl 属性设置为“Memo1” 。编译并运行这个程序,按快捷键 <ALT+M> ,就可以快速存取Memo1 控制的内容。这项技术不需要任何代码,可以应用到所有没有Caption 属性的控件上。

I 选择组件的父组件和多个组件

在Delphi 集成环境中,在设计窗体时,如果父组件是不可见的,要选择父组件就比较困难。其实有一个简单的方法:选择不可见组件的一个子组件,按<ESC> 键,就可以选中其父组件。

当窗体上有多个组件时,可以通过按下鼠标左键拖动鼠标,将虚线矩形框包围要选择的组件,就可以方便地选择它们。但是, 如果想选择放置在一个面板类组件(如TPanel )上的一组组件时,单击并拖动将移动这组组件下的父组件,达不到预期的效果。为了避免这种情况的发生,需要按下<Ctrl> 键,然后再执行上述操作。

另外,按下<Shift> 键,单击一个组件,可以选择或取消选择一个组件。这对需要选择不同面板组件上的子组件时十分有用。

J 移动组件或调整其大小

在窗体上移动组件或调整其大小时,有时希望一次一个象素点地进行。采用Object Inspector 来修改组件的left, top, width 和height 属性可以做到这一点。但是还有一种更为简单的方法,就是使用<Shift> 和<Ctrl> 键加上箭头键。按<Shift+ 箭头键> 组合键在箭头指向的方向上调整组件的大小;按<Ctrl+ 箭头键> 组合键在箭头指向的方向上移动组件。这两种组合键对选定的多个组件同时有效。

K 在TNotebook 组件的所有页面上显示组件

若希望在TNotebook 或TPageControl 组件的所有页面上显示某些组件(例如浏览数据库的列表框)时,不需要在在每个页面上重复设置这些组件,只需要首先建立它们,然后再添加TNotebook 或TPageControl 控件,调整它们的大小和位置,用鼠标右键单击TNotebook 或TPageControl 组件,单击弹出菜单中的“Send To Back” 属性,这时最先添加的控件就显示在TNotebook 或TPageControl 控件之上,按通常的方法添加其他组件到TNotebook 或TPageControl 组件即可。

此方法只对控件有效,所以TDBText 需用TDBEdit 代替,并设置其为只读,边界属性设置为空,Ctrl3D 属性设置为假。同理,需要用TPanel 组件代替TLabel 组件。

另一种更为有效的方法是编写一段代码,来动态改变组件的位置,这种方法对所有的组件均有效。以TPageControl 为例,在其OnChange 事件处理程序中插入如下代码:

procedure TForm1.PageControl1Change(Sender: TObject);

begin

Panel1.Parent := PageControl1.ActivePage;

//other code follows

end;

实际使用时,用自己的组件代替Panel1 。记住:应当将组件放置在程序运行时,打开对话框时首先显示的对话页上,以避免在窗体的OnCreate 事件处理程序中编写代码。

L 取消拖动操作

在设计窗体时,如果在移动一个组件的位置时,发现选错了组件,这时该怎么办呢?无疑,你想取消这一步的拖动操作,其实很简单,在没有释放鼠标键之前,按<ESC> 键,这个组件就会返回到原来的位置。

M 为Y2K 格式化tDateTimePickers 的日期显示

在Delphi 中使用TDateTimePicker.DateFormat 来指明日期格式,DateFormat 是TDTDateFormat 类型属性,取值为dfShort 或dfLong 。若取dfShort, 日期格式类似于是“3/21/97” ;若取dfLong, 日期格式类似于“Friday, March 21, 1997” 。

为了兼容Y2K 格式,需将日期格式设置为YYYY-MM-DD ,根据上面的解释,tDateTimePicker 组件在设置为短日期格式时就不敷应用了。但是, 如果你在控制面板中设置了短格日期式(使用区域设置),tDateTimePicker 将使用Windows 的设置,所以它还是可以使用的。

DELPHI 编程技巧集锦(2 )

董占山

( 中国农科院棉花研究所,河南安阳, 455112)

三、代码设计的相关技巧

A 使用特殊字符

应用程序有时需要用到键盘上没有的字符,例如,版权符号(© )、英镑符( £) 和日圆符( ¥) 等。为了输入这些字符,需要使用Windows 字模映射程序。

打开字模映射程序,从“字体”列表框中选中合适的字体,在下面的列表框选中一个字符,在窗口的右下角将显示出这个字符的ASCII 码值。例如英镑符的ASCII 码为0163 ,在键盘上按下<ALT> 键的同时按下0163 ,就可以输入英镑符。也可以使用字模映射程序的选择和复制按钮将选定字符复制到Windows 的剪贴板上,然后再使用“粘贴”命令或按<SHIFT+INS> 键盘命令将字符粘贴到目标程序代码中。

B 在代码中设置位置标记

Delphi 代码编辑器允许在源代码中放置一些位置标记,就向老式的WordStar 所具有的那种。使用位置标记的目的是快速地在文档不同位置之间进行切换。比如在创建一个类函数时,希望看一下它的声明部分,位置标记就派上用场了。在代码编辑器中设定位置标记的快捷键为:<CTRL+K>+<1-9 之间的任意数字> ,移动到已有位置标记的快捷键为:<CTRL+Q> + <1-9 之间的任意数字> 。在默认状态下,Delphi 并不保存用户在代码中设定的位置标记,为了让Delphi 将设定的位置标记保存到文件中,一便下次利用,需要在“Environment Options” 对话窗口的“Preferences” 对话页选中“Autosave” 复选项,这样Delphi 就将位置标记信息保存到项目的DSK 文件中。

C 使用键盘快捷键快速进行代码块缩进

在编辑程序源代码时,不同代码块之间保持不同的缩进距离,可以使代码易于阅读。当程序结构调整之后,需要调整代码的缩进量,通常我们使用上下光标键在不同代码行之间进行切换,用< 空格> 、<Tab> 和<Del> 键来增加或减少缩进空间。使用过Turbo Pascal 的老用户可能还记得它的集成编辑器提供了一组快捷键来快速切换代码块的缩进量,使用十分方便。其实,Delphi 集成编辑器也提供了两个组合键来快速增加或减少多行代码的缩进量。首先选择待改变缩进量的代码块,按<Ctrl+Shift+I> 组合键来扩展代码块的缩进量,按<Ctrl+Shift+U> 组合键来缩小代码块的缩进量。

D 在代码编辑窗口中选择一个矩形区域

大家知道在Microsoft Word 97 中可以选择一个矩形区域,在Delphi 的集成编辑器也有类似功能。为了选择一个矩形区域,按下<Alt> 键不放,然后用鼠标和键盘选择文本。

E 跳到VCL 源代码去

通过下面的方法,可以转跳到VCL 库例程的源代码:

  1. 按下<Ctrl> 键;

  1. 将鼠标光标移到想看其源码的类型声明的名字上;

  1. 单击之即可调出VCL 源程序代码进行查看。

F 在集成环境中记录击键并回放

在使用Delphi 编写程序时,由于需要多次输入同一个变量名称或一段固定的代码,你或许想过将这段代码的击键记录下来,在需要时回放它们,以实现快速编码,减少无效劳动,就象在DOS 时代使用F3 键回放刚刚输入的一行命令一样。Delphi 集成编辑器同样提供这项功能:按<Ctrl+Shift+R> 开始录制击键,然后键入你希望录制的击键,再按<Ctrl+Shift+R> 停止录制。按<Ctrl+Shift+P> 回放刚刚录制的击键。注意:这种功能仅仅在默认的编辑器键盘模式下有效。为了查找你使用了那种编辑器键盘模式,单击“Tools”*“Environment Options” 菜单项,单击“Editor” 标签,就可以在编辑器设置组合框中看到当前使用的编辑器键盘模式了。

G 代码模板

Delphi 的代码模板(Code Template )可以减少重复输入。在Delphi 编辑器中,按<CTRL+J> 键打开模板选择列表框;或者键入一个模板的名称,然后按<CTRL+J> 来扩展模板。

选择“Tools”*“Environment Options” 菜单命令,单击“Code Insight” 标签,可以添加自己的代码模板。用户可以输入任何代码,不仅仅是数组、循环等。模板在下列情况下十分有用:为过程、函数和方法所写的标准初始化代码、注释块或其他用途。

H 使用代码完成功能

Delphi 3/4 中一项易被忽视的功能是代码完成特征,该项功能弹出一个列表框,列出所有可能的赋值。下面的例子演示了这项功能。

开始一个新的项目,双击窗体,切换到代码窗口,编写窗体的OnCreate 事件处理程序,如下:

procedure TForm1.FormCreate(Sender: TObject);

var

temp : string;

temp2 : integer;

begin

end;

这时,在过程体中输入“temp :=” ,按<Ctrl+ 空格> 键,稍侯,就可以看到一个含有一些变量、方法和对象的列表,以及潜在的有效赋值。一些选择项后带有省略号,表明这些对象或记录含有兼容的方法或字段可以作为赋值。

四、数据库程序设计技巧

A 充分利用数据库窗体专家(Database Form Expert )

数据库窗体专家(Database Form Expert ),在Delphi 3 中称为向导,对所有版本的Delphi 都是有效的。窗体专家的用途是帮助用户快速地创建数据库应用程序。但是,不足的是它产生的窗体存在控制和字段位置、大小不适中的问题。但是,其优点还是很显然的。很明显,程序员需要和客户一起来设计窗体,使其更加美观实用。数据库窗体专家可以建立数据存取控件并完成他们的基本连接属性。虽然位置和大小不适中,它可以建立大多数数据输入字段和其标签,所以充分利用数据库窗体专家,可以节省设计窗体原型的时间。

B 将数据库转换为CSV 格式

如果想将数据库表格转换为以逗号分割的文本文件(CSV 格式),可以使用如下的过程代码:

procedure BackupTableToCSV(tableName:TTable);

var

i,j: integer; (*i-field, j-record*)

s: string; (*Record string*)

theStringList: TStringList; (*temp storage*)

begin

s:=\'\';

theStringList:=TStringList.Create;

with tableName do begin

try

Active:=True;

except

showmessage(\' 不能激活数据库:\'+ Name);

end;

for j:=0 to (RecordCount-1) do begin

s:=\'\';

for i:=1 to (FieldCount-1) do begin

(*add next field w/comma delimiter*)

s:=s+(Fields[i].AsString)+\',\';

end; (*i for*)

theStringList.add(s);

Next;

end; (*j for*)

theStringList.savetofile(Name+\'.csv\'); (*memo1.lines.*)

Showmessage(Name+ \' 已被转换完毕.\');

close;

end; (*with*)

end; (*BackupTableToCSV*)

C 动态更新DBGrid 的行颜色

DBGrid 是显示表格数据的好控件,本例旨在演示如何动态地改变其中的文本颜色。例如,我们想用DBGrid 中的行来显示国家的信息,如果国家的人口大于2 亿,数据行显示将为兰色。在 DBGrid 组件的OnDrawColumnCell 事件处理程序中测试数据并改变颜色,程序代码如下:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject;

const Rect: TRect; DataCol: Integer; Column: TColumn;

State: TGridDrawState);

begin

if Table1.FieldByName(\'Population\').AsInteger > 20000000 then

DBGrid1.Canvas.Font.Color := clBlue;

DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State);

end;

这是一项简单实用的技术,它除了可以显示数据内容,还可以显示信息的意义,例如太多人口、账号透支、零件到货等。

D 在程序运行时创建报告

使用Quick Report 提供的QRCreateList 函数,可以在运行时建立报告。下面是一个实例:

QRCreateList(aReport, Self, qryCountry, \'Country Report\', FieldList);

其中,aReport 为报告的名称;qryCountry 为数据表名称;\'Country Report\' 为报告题目;FieldList 为包含在报告中的字段列表,如果这个列表等于nil 或包含0 个项目,所有字段都将被使用。

在运行时建立一个报告的代码如下:

{ 默认的字段列表为nil }

FieldList := nil;

{ 确保新报告对象指向nil, 否则使用QRCreateList 函数建立报告时,将出错 }

aReport := nil;

{ 调用QRCreateList 函数建立报告,将自动建立一个含有列头带和细目带的报告,

用户可以在预览或打印前,添加组带、汇总带等内容 }

QRCreateList(aReport, Self, qryCountry, \'Country Report\', FieldList);

E 巧用字段编辑器

字段编辑器(Fields Editor )除了可以建立永久的字段对象外,还可以帮助程序员迅速将数据库控件放置到窗体上。方法如下:

从字段编辑器将一个字段名拖放到窗体上,当你松开鼠标键时,Delphi 就向窗体上添加一个TLabel 和一个TDBEdit 控件。这些被建立的控件使用在字段编辑器中设定的属性:包括Alignment 、DisplayLabel 、DisplayWidth 和 EditMask 属性。当然,DBEdit 的DataSource 和DataField 属性也被设置好了。

F 加速数据库的搜索过程

想增加数据库的检索速度吗?在进行数据检索之前,调用数据表的DisableControls 方法,将DataSet 和 DataSource 组件的联系断开,当检索结束时,调用数据表的EnableControls 方法,重新在DataSet 和 DataSource 组件之间建立联系,这样就可以节省更新数据控制的时间,从而加速检索的速度。下面是一个实例:

unit Unit1;

.

.

type

TForm1 = class(TForm)

DataSource1: TDataSource;

Table1: TTable;

Button1: TButton;

.

.

procedure TForm1.Button1Click(Sender: TObject);

var

SeekValue: string;

begin

Table1.DisableControls;

Table1.FindKey([SeekValue]);

Table1.EnableControls;

end;

end.

G 增强数据表的处理能力

对使用数据表的应用程序,怎样在减少代码维护的同时增加程序的性能?使用Delphi 的数据模块就可以做到这一点。方法如下:

1 在程序计划中添加一个数据模块(Data Module );

2 将数据表存取组件放到数据模块窗体上:为应用程序使用的每个数据表都添加一个TTable 和TDataSource 组件到数据模块窗体上,并正确设置它们的DatabaseName 、TableName 和DataSet 属性。

3 在使用数据表的每个窗体上加入对数据模块单元的应用,这样就可以在这些窗体上使用数据控制组件了,将这些组件的DataSource 设置为数据模块的合适的TDataSource 组件。

使用数据模块窗体将所有数据表都集中起来后,有以下三个优点:第一,免去了向每个窗体均添加数据表存取组件;第二,如果同一数据字段在不同的窗体中使用并修改,这样的修改在不同的窗体间是共享的,而且不需要增加任何代码;第三,由于程序减少了在不同窗体上校验同一数据表的代码,所以程序的性能达到一定的改善。

DELPHI 编程技巧集锦(3 )

董占山

( 中国农科院棉花研究所,河南安阳, 455112)

五、操作系统相关的技巧

A 如何决定Windows 的版本

Windows 具有多个版本,一个应用程序或者具有运行在多个Windows 版本下的灵活性,或者通过条件编译指令,编译成只能在一个操作平台下工作。

下面介绍一种方法,可以使应用程序能动态地决定Windows 操作系统的版本。应用程序通过调用Windows API 函数GetVersionEx 可以获得Windows 的版本信息,该函数使用一个TOSVersionInfo 类的变参,所有Windows 版本信息就包含在其中,其结构如下:

typedef struct _OSVERSIONINFO{

DWORD dwOSVersionInfoSize; // 结构的大小

DWORD dwMajorVersion; // 主版本

DWORD dwMinorVersion; // 副版本

DWORD dwBuildNumber; // 建立版本

DWORD dwPlatformId; // 操作平台标识

TCHAR szCSDVersion[ 128 ]; // 版本标识字符串

} OSVERSIONINFO;

下面是使用该函数的一个例子:

procedure TForm1.Button1Click(Sender: TObject);

var

VersionInfo: TOSVersionInfo;

begin

VersionInfo.dwOSVersionInfoSize := Sizeof(TOSVersionInfo);

GetVersionEx(VersionInfo);

case VersionInfo.dwPlatformID of

VER_PLATFORM_WIN32S:

Do_SomeThing;

VER_PLATFORM_WIN32_WINDOWS:

Do_SomeOtherThing;

VER_PLATFORM_WIN32_NT:

Do_SomeThingElse;

end;

end;

B 内存知多少?

下面介绍一种方法可以决定系统内存的多少、使用状态等信息。更重要的是,应用程序可以利用这项技术来决定客户机的可用内存的大小,利用这些信息,应用程序可以动态地优化程序的性能。例如,如果有足够的内存可以利用双缓存优化位图的操作。

利用Windows API 函数GlobalMemoryStatus 可以完成上述功能。GlobalMemoryStatus 接收一个类型为TMemoryStatus 的变参,通过这个参数就可以获得Windows 当前的内存状态。TMemoryStatus 的结构如下:

typedef struct _MEMORYSTATUS { // mst

DWORD dwLength; // sizeof(MEMORYSTATUS) ,该记录结构的大小

DWORD dwMemoryLoad; // 使用内存所占百分比

DWORD dwTotalPhys; // 物理内存的字节数

DWORD dwAvailPhys; // 自由物理可用内存字节数

DWORD dwTotalPageFile; // 页文件字节数

DWORD dwAvailPageFile; // 页文件的自由字节数

DWORD dwTotalVirtual; // 地址空间的用户字节数

DWORD dwAvailVirtual; // 自由用户字节数

} MEMORYSTATUS, *LPMEMORYSTATUS;

下面是使用GlobalMemoryStatus 函数的一个例子:

procedure TForm1.Button1Click(Sender: TObject);

var

MemoryStatus: TMemoryStatus;

begin

MemoryStatus.dwLength := sizeof(MemoryStatus);

GlobalMemoryStatus(MemoryStatus);

Label1.Caption := \'Total Physical Memory: \' + IntToStr(MemoryStatus.dwTotalPhys);

end;

C 获得消逝时间

在测试硬件或软件的效率时或跟踪用户的响应速度时,需要测定消逝的时间。多数程序员使用一个TDateTime 变量和Now 函数来实现测定消逝时间的目的。

但是,一种更简单的方法是使用Windows API 函数GetTickCount 。GetTickCount 函数返回从启动Windows 后消逝的毫秒数。如果函数成功地返回,返回值就是从启动Windows 后消逝的毫秒数。下面是一个使用实例:

procedure TForm1.Button1Click(Sender: TObject);

var

i: longint;

StartTime, EndTime: Double;

const

CLOCK_TICK: Double = 1000;

begin

i := 0;

StartTime := GetTickCount;

while (i < 10000000) do i:= i+1;

EndTime := GetTickCount - StartTime;

ShowMessage(Format(\' 消逝时间: %0.2f 秒\',[EndTime/CLOCK_TICK]));

end;

D 隐藏/ 显示Windows 95 的任务栏

想不想让你编写的Delphi 程序具有隐藏/ 显示Windows 95 任务栏的功能,在程序中使用下面的两个过程就可以实现这一功能。

procedure hideTaskbar;

var wndHandle : THandle;

wndClass : array[0..50] of Char;

begin

StrPCopy(@wndClass[0], \'Shell_TrayWnd\');

wndHandle := FindWindow(@wndClass[0], nil);

// 隐藏任务栏

ShowWindow(wndHandle, SW_HIDE);

end;

procedure showTaskbar;

var wndHandle : THandle;

wndClass : array[0..50] of Char;

begin

StrPCopy(@wndClass[0], \'Shell_TrayWnd\');

wndHandle := FindWindow(@wndClass[0], nil);

// 显示任务栏

ShowWindow(wndHandle, SW_RESTORE);

end;

E 捕获文件的日期和时间标志

希望显示文件的日期和时间标志吗?Delphi 中没有一个简单的函数来完成这项功能,但是我们可以将两个函数结合起来实现这一功能。

首先,FileGetDate 函数返回文件的DOS 日期和时间,然后,FileDateToDateTime 函数将日期和时间转换为TDateTime 类型的变量,最后,DateTimeToStr 过程将TDateTime 类型的变量转换为字符串。实例如下:

procedure TForm1.Button1

var

TheFileDate: string;

Fhandle: integer;

begin

FHandle := FileOpen(YourFileName, 0);

try

TheFileDate :=

DateTimeToStr(FileDateToDateTime(FileGetDate(FHandle)));

finally

FileClose(FHandle);

end;

end;

使用DateTimeToStr 的格式化参数可以调整输出结果的形式。即使你不需要显示日期和时间,也可以使用这项技术比较和计算文件日期。

F 避免驱动器A 没有准备好错误(Not Ready error )

当你的程序存取A 驱动器时,可能会被\'Drive Not Ready\' 系统错误所中断,可以使用下面的函数来测试驱动器,以避免这种情况发生,代码如下:

function DiskInDrive(Drive: Char): Boolean;

var

ErrorMode: word;

begin

Drive: = UpCase(Drive);

if not (Drive in [\'A\'..\'Z\']) then

raise EConvertError.Create(\'Not a valid drive ID\');

ErrorMode := SetErrorMode(SEM_FailCriticalErrors);

try

if DiskSize(Ord(Drive) - $40) = -1 then

DiskInDrive := False

else

DiskInDrive := True;

finally

SetErrorMode(ErrorMode);

end;

end;

本函数的工作原理是:首先将驱动器符转换为大写字母,然后关闭系统错误报告功能,执行磁盘操作,操作成功返回True ,表明驱动器里存在磁盘;操作失败返回False ,表明发生错误,函数结束时打开系统错误报告功能。

G 隐藏应用程序

假如你不仅想让应用程序隐藏窗体,同时不想让应用程序在任务栏上显示,可以使用如下命令:

ShowWindow (Application.handle, SW_HIDE);

这条命令对使用托盘区(System Tray )图标来激活的应用程序十分有用。

H 重定向DOS 应用程序

有时,你需要重定向一个DOS 应用程序。下面的代码可以帮助你完成这项工作:

{---------------------CreateDOSProcessRedirected------------------

Description : executes a (DOS!) app defined in the CommandLine

parameter redirected to take input from InputFile

and give output to OutputFile

Result : True on success

Parameters :

CommandLine : the command line for the app,

including its full path

InputFile : the ascii file where from the app

takes input

OutputFile : the ascii file to which the app\'s

output is redirected

ErrMsg : additional error message string.

Can be empty

Error checking : YES

Target : Delphi 2, 3, 4

Author : Theodoros Bebekis, email bebekis@otenet.gr

Notes :

Example call :

CreateDOSProcessRedirected(\'C:/MyDOSApp.exe\',

\'C:/InputPut.txt\',

\'C:/OutPut.txt\',

\'Please, record this message\')

------------------------------------------------------------------}

function CreateDOSProcessRedirected(const CommandLine, InputFile,

OutputFile, ErrMsg :string):boolean;

const

ROUTINE_ID = \'[function: CreateDOSProcessRedirected ]\';

var

OldCursor : TCursor;

pCommandLine : array[0..MAX_PATH] of char;

pInputFile,

pOutPutFile : array[0..MAX_PATH] of char;

StartupInfo : TStartupInfo;

ProcessInfo : TProcessInformation;

SecAtrrs : TSecurityAttributes;

hAppProcess,

hAppThread,

hInputFile,

hOutputFile : THandle;

begin

Result := False;

{ Check for InputFile existence }

if not FileExists(InputFile)

then

raise Exception.CreateFmt(ROUTINE_ID + #10 + #10 +

\'Input file * %s *\' + #10 +

\'does not exist\' + #10 + #10 +

ErrMsg, [InputFile]);

{ Save the cursor }

OldCursor := Screen.Cursor;

Screen.Cursor := crHourglass;

{ Copy the parameter Pascal strings to null terminated

strings }

StrPCopy(pCommandLine, CommandLine);

StrPCopy(pInputFile, InputFile);

StrPCopy(pOutPutFile, OutputFile);

TRY

{ Prepare SecAtrrs structure for the CreateFile calls.

This SecAttrs structure is needed in this case because

we want the returned handle can be inherited by child

process. This is true when running under WinNT.

As for Win95, the documentation is quite ambiguous }

FillChar(SecAtrrs, SizeOf(SecAtrrs), #0);

SecAtrrs.nLength := SizeOf(SecAtrrs);

SecAtrrs.lpSecurityDescriptor := nil;

SecAtrrs.bInheritHandle := True;

{ Create the appropriate handle for the input file }

hInputFile := CreateFile(

pInputFile,

pointer to name of the file }

GENERIC_READ or GENERIC_WRITE,

access (read-write) mode }

FILE_SHARE_READ or FILE_SHARE_WRITE,

share mode }

@SecAtrrs,

pointer to security attributes }

OPEN_ALWAYS,

{how to create }

FILE_ATTRIBUTE_NORMAL

or FILE_FLAG_WRITE_THROUGH,

{ file attributes }

0 ); handle to file with attributes to copy }

{ Is hInputFile a valid handle? }

if hInputFile = INVALID_HANDLE_VALUE

then

raise Exception.CreateFmt(ROUTINE_ID + #10 + #10 +

\'WinApi function CreateFile returned an\' +

\'invalid handle value\' + #10 +

\'for the input file * %s *\' + #10 + #10 +

ErrMsg, [InputFile]);

{ Create the appropriate handle for the output file }

hOutputFile := CreateFile(

pOutPutFile,

pointer to name of the file }

GENERIC_READ or GENERIC_WRITE,

access (read-write) mode }

FILE_SHARE_READ or FILE_SHARE_WRITE,

share mode }

@SecAtrrs,

pointer to security attributes }

CREATE_ALWAYS,

{ how to create }

FILE_ATTRIBUTE_NORMAL

or FILE_FLAG_WRITE_THROUGH,

file attributes }

0 );

handle to file with attributes to copy }

{ Is hOutputFile a valid handle? }

if hOutputFile = INVALID_HANDLE_VALUE

then

raise Exception.CreateFmt(ROUTINE_ID + #10 + #10 +

\'WinApi function CreateFile returned an\' +

\'invalid handle value\' + #10 +

\'for the output file * %s *\' + #10 + #10 +

ErrMsg, [OutputFile]);

{ Prepare StartupInfo structure }

FillChar(StartupInfo, SizeOf(StartupInfo), #0);

StartupInfo.cb := SizeOf(StartupInfo);

StartupInfo.dwFlags := STARTF_USESHOWWINDOW or

STARTF_USESTDHANDLES;

StartupInfo.wShowWindow := SW_HIDE;

StartupInfo.hStdOutput := hOutputFile;

StartupInfo.hStdInput := hInputFile;

{ Create the app }

Result := CreateProcess(nil,

{ pointer to name of executable module }

pCommandLine,

{ pointer to command line string }

nil,

{ pointer to process security attributes }

nil,

{ pointer to thread security attributes }

True,

{ handle inheritance flag }

HIGH_PRIORITY_CLASS,

{ creation flags }

nil,

{ pointer to new environment block }

nil,

{ pointer to current directory name }

StartupInfo,

{ pointer to STARTUPINFO }

ProcessInfo);

{ pointer to PROCESS_INF }

{ wait for the app to finish its job and take the

handles to free them later }

if Result

then

begin

WaitforSingleObject(ProcessInfo.hProcess, INFINITE);

hAppProcess := ProcessInfo.hProcess;

hAppThread := ProcessInfo.hThread;

end

else

raise Exception.Create(ROUTINE_ID + #10 + #10 +

\'Function failure\' + #10 + #10 +

ErrMsg);

FINALLY

{ Close the handles.

Kernel objects, like the process and the files

we created in this case, are maintained by a usage

count. So, for cleaning up purposes, we have to

close the handles to inform the system that we don\'t

need the objects anymore }

if hOutputFile <> 0 then CloseHandle(hOutputFile);

if hInputFile <> 0 then CloseHandle(hInputFile);

if hAppThread <> 0 then CloseHandle(hAppThread);

if hAppProcess <> 0 then CloseHandle(hAppProcess);

{ Restore the old cursor }

Screen.Cursor:= OldCursor;

END;

end; { CreateDOSProcessRedirected }

DELPHI 编程技巧集锦(4 )

董占山

( 中国农科院棉花研究所,河南安阳, 455112)

I 在程序中启动其他程序的方法

使用ShellExecute 函数(在单元shellapi 中)可以执行一个程序或调入一个文件,不论这个文件是执行程序还是图象或文档等。函数的使用方法如下:

ShellExecute(Handle,\'open\',PChar(Edit1.Text),\'\',\'\',SW_SHOWNORMAL);

ShellExecute(Handle,\'open\', \'c:/doc/bar.doc\' ,\'\',\'\',SW_SHOWNORMAL);

它的效果类似于在Windows 资源管理器中双击了一个文件。如果执行函数成功,返回值就是打开的应用程序例程的句柄,或者是DDE 服务器应用程序的句柄。如果执行函数失败,返回值是一个小于等于32 的错误号。将\'open\' 换为\'print\' ,这个函数就可以打印指定的文件了。

J 利用系统图象列表

假如你需要存取WIN95 的系统图象列表,这里给出具体方法。第一个函数将系统图象列表的索引保存到一个特殊类型的文件中:

function GetFileIcoIndex(Filename:String):Integer;

var

Ext: String;

ShFileInfo: TSHFILEINFO;

begin

Ext := filename;

ShGetFileInfo(PChar(Ext), 0, SHFileInfo,

SizeOf(SHFileInfo), SHGFI_SMALLICON or

SHGFI_SYSICONINDEX or SHGFI_TYPENAME);

result:= SHFileInfo.iIcon;

end;

下面将系统图象列表连接到TListView 控件上。注意我们设置动态建立的图象列表的ShareImages 属性为真,这可以确保我们不试图释放Windows 系统拥有的图象。在窗体的OnCreate 事件处理程序中加上:

with YourListView do

begin

SmallImages := TImageList.CreateSize(16,16);

SmallImages.ShareImages := True;

SmallImages.Handle := ShGetFileInfo(\'*.*\', 0,

SHFileInfo, SizeOf(SHFileInfo), SHGFI_SMALLICON or

SHGFI_ICON or SHGFI_SYSICONINDEX);

LargeImages := TImageList.Create(nil);

LargeImages.ShareImages := True;

LargeImages.Handle := ShGetFileInfo(\'*.*\', 0,

SHFileInfo, SizeOf(SHFileInfo), SHGFI_LARGEICON or

SHGFI_ICON or SHGFI_SYSICONINDEX);

end;

关闭窗体时,在其OnDestroy 事件处理程序中加上以下代码,释放申请的系统资源:

YourListView.SmallImages.Free;

YourListView.LargeImages.Free;

K 使你的窗体保留在桌面的最上面

当我们想让一个窗体保留在桌面的最上面时,可以定义窗体的FormStyle 属性,使窗体保持在最上面。但是,使用这种方法后,在切换窗体的模式时,窗体将闪烁。为了避免切换窗体模式时的闪烁,可以使用Windows API 函数SetWindowPos 来解决这一问题,使用方法如下:

SetWindowPos(Form1.handle, HWND_TOPMOST, Form1.Left, Form1.Top, Form1.Width, Form1.Height,0);

用实际窗体名称代替"Form1" ,调用这个命令就可以将窗体设置为保留在桌面的最上面。如要将窗体切换回正常的窗体,调用下面的命令:

SetWindowPos(Form1.handle, HWND_NOTOPMOST, Form1.Left, Form1.Top, Form1.Width, Form1.Height,0);

L 跟踪Windows 的临时目录

Window 95 和NT 都使用一个目录来保存临时文件,这个临时目录不是固定不变的,为了确保应用程序使用正确的临时文件目录,可以使用Windows API 函数GetTempPath 来获取这个目录的路径,使用格式如下:

DWORD GetTempPath(DWORD nBufferLength, LPTSTR lpBuffer );

为了方便使用这个Windows API 函数获取临时文件目录路径,编写了一个Object Pascal 函数:

function GetTempDirectory: String;

var

TempDir: array[0..255] of Char;

begin

GetTempPath(255, @TempDir);

Result := StrPas(TempDir);

end;

使用GetTempPath 可以获得TMP 环境变量指明的路径;如果TMP 没有定义,可以获得TEMP 环境变量指明的路径;若TMP 和TEMP 都不存在,就使用当前目录作为临时目录。

M 处理自己的热键

应用程序可以使用许多Windows 默认的热键。但是,有时需要向窗体添加自己的热键。当用户键入他们时,怎样捕获它们呢?为了解决这个问题,首先应将窗体的KeyPreview 属性设置为True ,然后在窗体的OnKeyDown 事件处理程序中添加如下代码:

if (ssCtrl in Shift) and (chr(Key) in [\'A\', \'a\']) then ShowMessage(\'Ctrl-A\');

OnKeyDown 事件处理程序将捕获击键,并执行指定的代码。

六、Object Pascal 编程技巧

A 调用基类初始化例程的简便方法

假如编写一个衍生类的初始化例程,并希望将参数传递给基类的初始化例程,一般使用如下方法:

TMyButton.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

{ Do custom things... }

end;

但是,如果衍生类的初始化例程与基类有相同的参数,就不必明确地指出基类的初始化方法和参数,只需要按如下方法调用即可:

TMyButton.Create(AOwner: TComponent);

begin

inherited;

{ Do custom things... }

end;

B 安全的数组循环

一个Object Pascal 数组是一组同类元素的有序集合。如果你不知道数据的上下界值,怎么来存取其中的元素呢?使用Low 和High 函数可以保证你存取其中的每个元素,请看下面的实例:

var

MyArray : array[2..11] of integer;

Position : integer;

begin

for Position := Low(MyArray) to High(MyArray) do

MyArray[Position] := 0;

end;

使用这项技术,代码中不包含任何立即数,程序代码就相对独立于数组的上下界区间。另外,由于这两个函数调用在编译时就转换为常数,故不存在任何执行代价。

C 确保字段有效

将验证字段有效性的代码放在关闭窗体的按钮的OnClick 事件处理程序中是很自然的事情。但是,为了确保在任何情况下都能验证字段的有效性,需要使用窗体的OnCloseQuery 事件。这样,无论怎样关闭程序,都能保证字段得到验证,并保证了代码的唯一性。

procedure TForm1.FormCloseQuery(Sender: TObject;

var CanClose: Boolean);

begin

CanClose := False;

if (Edit1.Text = \'\') then begin

{ 错误处理}

end

else

if (RadioGroup1.ItemIndex < 0) then begin

{ 错误处理}

end

else

CanClose := True;

end;

在过程的起始处设置CanClose 属性为False ,确保OnCloseQuery 不关闭窗体。然后,验证每一个字段并处理错误。最后,如果没有错误发生,设置CanClose 为True 。

D 快速定制程序的动态菜单

在编制应用程序时,我们有时需要根据用户的使用水平来动态地定制程序菜单,通常的做法是切换菜单项目(TMenuItem )组件的Enabled 属性,从而禁止或打开一个菜单项目,这样做起来十分复杂并且容易出错。其实,通过给每个菜单项目的tag 属性赋值,并根据用户的水平加以判断,就可以快速安全地定制程序菜单。方法如下:

1 按正常的方法建立菜单;

2 根据用户水平,给每个菜单项目的tag 属性赋值,例如,将“文件”*“ 打开”和“文件”*“ 关闭”的属性设置为2 ,将“文件”*“ 新建”、“文件”*“ 保存”和“文件”*“ 打印”的tag 属性设置为3 ;

3 在窗体的OnCreate 事件处理程序中加上下列代码:

for i := 0 to MainMenu1.Items.Count -1 do

begin

if UserLevel < MainMenu1.Items[i].Tag then

MainMenu1.Items[i].Visible := False

else

MainMenu1.Items[i].Visible := True;

for j := 0 to MainMenu1.Items[i].Count -1 do

if UserLevel < MainMenu1.Items[i].Items[j].Tag then

MainMenu1.Items[i].Items[j].Visible := False

else

MainMenu1.Items[i].Items[j].Visible := True;

end;

4 这段代码可以根据用户的使用水平动态地决定哪个菜单项目显示或哪个菜单项目不显示。

上述代码只能动态地开关一个菜单的菜单条目,如果要同时动态改变所有菜单的菜单条目,需要使用窗体的Components 属性,代码如下:

for i := 0 to ComponentCount -1 do

if Components[i] is TMenuItem then

TMenuItem(Components[i]).Visible :=

(UserLevel >= TMenuItem(Components[i]).Tag);

这段代码更加紧凑高效,是定制应用程序菜单的良好工具。

E 验证用户输入的日期

有经验的程序员知道不应该盲目接受用户输入的日期。诚然,你可以检查日期的年、月、日是否正确。但是,你检查日期是否实际存在?例如,假设用户输入9/31/97 ,月、日、年分别都是有效的,但是9 月没有31 号。为了检查一个日期字符串是否有效,可以使用如下代码:

var adatetime : tdatetime;

...

try

adatetime:=StrToDate(inputdatestring);

except

// EConvertError 错误 - 无效的日期或日期格式

end;

这段代码对瑞年也同样有效。

F 建立全局条件定义

在编译程序时,用条件定义来决定包含或排除一块代码,由编译器决定哪块代码将是执行文件的一部分。通常的条件定义包括发送调试信息到一个文件和对Windows 95 或Windows NT 优化。下面是一个条件定义的实例:

unit Unit1;

{$DEFINE MYDEF1} // 建立自己的条件定义

{$IFDEF MYDEF1} // 如果宣布了条件定义

// 更新标签的显示

Label1.Caption := \'MYDEF1 is declared.\';

{$ENDIF}

有时,需要在一个项目的多个单元中使用同一个条件定义,可以采用下面的方法:

  1. 单击“Project”*“Options” ,弹出Project Options 对话窗口;

  2. 单击“Directories/Conditionals” 标签;

  3. 在“Conditional Defines” 编辑框中输入MYDEF1 ;

  4. 单击“Ok” 按钮返回即可。

G 为TListBox 组件添加水平滚动条

Delphi 的TListBox 组件会自动添加一个垂直滚动条,即当列表框的高度容纳不下所有的列表条目时,垂直滚动条就自动显示。但是,当条目的宽度大于列表框的宽度时,水平滚动条不会自动显示。当然, 可以在列表框中加如水平滚动条,方法是在窗体的OnCreate 事件处理程序中加入如下代码:

procedure TForm1.FormCreate(Sender: TObject);

var

i, MaxWidth: integer;

begin

MaxWidth := 0;

for i := 0 to ListBox1.Items.Count - 1 do

if MaxWidth < ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i]) then

MaxWidth := ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i]);

SendMessage(ListBox1.Handle, LB_SETHORIZONTALEXTENT, MaxWidth+2, 0);

end;

这段代码先查找列表框中最长的条目的宽度(以象素点表示),然后, 用LB_SETHORIZONTALEXTENT 消息来设置列表框的水平滚动条的宽度(以象素点表示),外加两个额外的象素。

H 用MessageDlg 函数显示信息的技巧

Delphi 提供了一种简单的方法显示信息对话窗口--- 使用MessageDlg 函数在屏幕窗口中心显示一个对话窗口。MessageDlg 函数的声明如下:

function MessageDlg(const Msg: string; AType: TMsgDlgType;

AButtons: TMsgDlgButtons; HelpCtx: Longint): Word;

从其函数声明中,看不出它可以显示可变的信息。但并非如此,它可以显示可变信息,通过将变量连接到显示信息字符串中,而且可以在字符串中加入#13 ,使其后的信息在新的一行上显示。请看实例:

procedure TForm1.Button1Click(Sender: TObject);

var

ErrorDescr: string;

begin

ErrorDescr := \'Call 911\';

MessageDlg(\'Error Code: 123\'+#13+ErrorDescr,mtInformation,[mbOk],0);

end;

I 动态删除TListView 组件的列

如果曾经试着在运行时删除一个TListView 组件的列,就知道它不允许这样做。真的不能吗?这里介绍一个文档中没有介绍的Delphi 函数,使用它可以做到这一点。这个函数在CommCtrl 单元中定义,函数声明如下:

function ListView_DeleteColumn(hwnd: HWND; iCol: Integer): Bool;

其中,hwnd 是TListView 组件的句柄,iCol 是欲删除的列的索引号,注意索引号是从零开始计算的。下面给出一个使用实例:

procedure TForm1.Button1Click(Sender: TObject);

begin

ListView_DeleteColumn(MyListView.Handle, 1);

end;

J 存取保护类属性

有时,需要存取在别处声明的保护类属性,最简单的方法是在自己的单元中使用下面的声明:

TExposed<classname> = class(<classname>);

例如:

TExposedWinControl = class(TWinControl);

因为TExposedbutton 现在是在你的单元中声明的,所以你可以存取被保护的所有属性--- 包括继承下来的。怎么使用呢?使用实例如下:

if Sender is TWinControl then

with TExposedWinControl(Sender) do

begin

//Caption & Text 在TWinControl 中是一样的

ShowMessage(Caption);

Text := \'Easy, isn\'\'t it?\';

end;

K 在调试程序时使用Format 代替IntToStr 函数

如果你以前是一个Visual Basic 用户,当显示调试信息时,你可能在MsgBox 函数中使用Format$ 来显示变量,但是,在Delphi 中, Format 函数的工作方式相当不同,所以许多用户借助于下面的代码:

ShowMessage(\'Undocumented feature in \' + Application.EXEName +

\' Variable now equal to \' + IntToStr(i_var));

为了使用Format 函数, 需要知道使用哪个Format 串。Format 串看起来十分复杂,但是仅仅需要知道其中的3 个,它们相当于占位符号,将会用Format 函数的第二个参数中的变量来替代。这3 个字符串是:

%d -- 代表一个整型数

%n -- 代表一个浮点数

%s -- 代表一个字符串

因此,上面的例子将改为:

ShowMessage(Format(\'Undocumented feature in %s. Variable is now equal to %d\',

[Application.EXEName,i_var]))

它们的结果是一样的,但是代码更加清晰并且容易修改。

当要显示大量变量时,Format 函数就显示出其真正的威力,见下例:

ShowMessage(Format(\'V1=%d and F2=%n in routine %s\',[V1,F2,\'UNIT2.PAS\']));

另外,用MessageBox 代替MessageDlg 或ShowMessage 函数可以压缩EXE 文件的大小,也可以使用Format 函数做到这一点,例子如下:

Var sz:Array[0..255] of Char;

MessageBox(0,StrPCopy(sz,Format(\'V1=%d and F2=%n in routine %s\',

[V1,F2,\'UNIT2.PAS\'])),\'DEBUG\',64);

一旦你开始应用Delphi 的Format 函数,你就会越来越少地借助于IntToStr 函数!

DELPHI 编程技巧集锦(5 )

董占山

( 中国农科院棉花研究所,河南安阳, 455112)

L 使用非VCL 类

在Delphi 中重用代码是十分容易的,但是写一个存取常用代码的VCL 是十分复杂的,为什么不写一个类单元呢?在一个大型项目中,维护和调试一个类是比较容易的,因为每个类是自包含的,没有全局变量的干扰。下面是一个类的框架:

unit cls_mine;

interface

Uses WinTypes,Winprocs,Messages, SysUtils, Classes;

Type

TMYCLASS = class(TObject)

private

{ Private Variables and Hidden Functions }

fstarted:Boolean

fflag:Boolean;

Procedure SetFlag(truefalse:Boolean);

Function GetFlag:Boolean;

public

{ Public Methods and Properties}

Function Init:Boolean;

Function GetExeDirectory:String

Property Started Read fstarted Write fstarted;

Property MyFlag Read GetFlag Write SetFlag;

end;

implementation

Function TMYCLASS.Init:boolean;

begin

fstarted:=True;

.. initialise stuff ..

end;

etc.etc.

当使用类时,在Uses 子句中加上cls_mine ,声明一个TMYCLASS 类型的变量MYCLASS ,然后调用MYCLASS.Create 建立类(记住在不使用类时调用MYCLASS.Free 注销类),你能够使用其方法和属性,如EXEDIR:=MYCLASS.GetEXEDirectory 或MYCLASS.MyFlag:=True ,就象使用VCL 类一样,不需要每次重新编译这个单元。

M 获得TMEMO 组件中光标所在的行数

如果TMEMO 能够告诉你光标在哪一行不是很好吗?但是,当你单击TMEMO 组件时,它设置SelStart 属性为当前光标的字符位置,这是TMEMO 中所有文本的一个位置索引值,你需要计算行长并测试SelStart ,将其翻译为行数,使用Windows API 函数可以很容易地获得TMEMO 组件中光标所在的行数:

LineNumber:=SendMessage(Memo1.Handle,EM_LINEFROMCHAR,memo1.Selstart,0);

LineNumber 是一个LongInt 型变量,可以将它转换为一个Integer 型变量。

N 让用户选择所有的项目

为了允许用户在记忆组件和编辑组件中可以按<CTRL+A> 来选择所有的文本,设置窗体的KeyPreview 属性为真,并为窗体的OnKeyPress 事件编写如下的处理程序:

procedure TMyForm.FormKeyPress(Sender: TObject;

var Key: Char); begin

if (ActiveControl is TCustomEdit) and (Key=#1) then

begin (ActiveControl as TCustomEdit).SelectAll;

Key:=#0; end

end;

Key:=#0 语句使在非文本输入时强迫程序发嘟嘟声。

O 怎样在RichEdit 组件中获得一段文本

在使用RichEdit 组件时,希望仅仅获得其中的一部分文本,但是不想设置选择区间和使用SelText 属性,可以使用如下代码实现:

{overrides wrong TTextRange definition in RichEdit.pas}

TTextRange = record

chrg: TCharRange;

lpstrText: PAnsiChar;

end;

function REGetTextRange(RichEdit: TRichEdit; BeginPos, MaxLength: Integer): string;

{RichEdit - RichEdit 控件,BeginPos - 第一个字符的绝对索引值,MaxLength - 获取的最大字符数}

var

TextRange: TTextRange;

begin

if MaxLength>0 then

begin

SetLength(Result, MaxLength);

with TextRange do

begin

chrg.cpMin := BeginPos;

chrg.cpMax := BeginPos+MaxLength;

lpstrText := PChar(Result);

end;

SetLength(Result, SendMessage(RichEdit.Handle,

EM_GETTEXTRANGE, 0, longint(@TextRange)));

end

else Result:=\'\';

end;

这个函数能够用来提取当前光标下的单词:

function RECharIndexByPos(RichEdit: TRichEdit;

X, Y: Integer): Integer;

{ function returns absolute character position }

{ for given cursor coordinates }

var

P: TPoint;

begin

P := Point(X, Y);

Result := SendMessage(RichEdit.Handle,

EM_CHARFROMPOS, 0, longint(@P));

end;

function REExtractWordFromPos(RichEdit: TRichEdit;

X, Y: Integer): string;

{ X, Y - point coordinates in rich edit control }

{returns word , under current cursor position}

var

BegPos, EndPos: Integer;

begin

BegPos := RECharIndexByPos(RichEdit, X, Y);

if (BegPos < 0) or

(SendMessage(RichEdit.Handle,EM_FINDWORDBREAK,

WB_CLASSIFY,BegPos) and

(WBF_BREAKLINE or WBF_ISWHITE) <> 0 ) then

begin

result:=\'\';

exit;

end;

if SendMessage(RichEdit.Handle, EM_FINDWORDBREAK,

WB_CLASSIFY, BegPos - 1) and

(WBF_BREAKLINE or WBF_ISWHITE) = 0 then

BegPos := SendMessage(RichEdit.Handle,

EM_FINDWORDBREAK, WB_MOVEWORDLEFT, BegPos);

EndPos := SendMessage(RichEdit.Handle,

EM_FINDWORDBREAK, WB_MOVEWORDRIGHT, BegPos);

Result := TrimRight(REGetTextRange(RichEdit, BegPos,

EndPos - BegPos));

end;

P 在Delphi 3 中如何使用interface 类型询问所有的窗体

一个窗体是一个TComponent 组件, 它提供了一个COM 对象(VCLComObject/ComObject) 包裹器。因此, 它不需要_AddRef/_Release 自己, 但是需要_AddRef/_Release 它包裹的对象。在Delphi 3 中,这意味着组件(窗体)需要实现一个哑对象,用来进行参考计数。在Delphi 4 中, 就不需要赋值一个哑对象,因为Delphi 只有在对象被赋值时才_AddRef/_Release 它们。下面的例子就一个IShowMe 界面类型询问所有的窗体:

procedure ExecuteShowMeOnAllForms;

var

Idx : integer;

ShowMeObject : IShowMe;

ObjectAssigned : boolean;

RefCountedObject : IUnknown;

begin

RefCountedObject := TInterfacedObject.Create;

for Idx := Screen.FormCount - 1 downto 0 do

with Screen.Forms[Idx] do begin

// Find out if we need to assign a VCLComObject.

ObjectAssigned := not Assigned (VCLComObject);

if ObjectAssigned then

VCLComObject := Pointer (RefCountedObject);

try

// GetInterface calls ShowMeObject\'s _Release

// & _AddRef, which is

// implemented by RefCountedObject.

if GetInterface (IShowMe, ShowMeObject) then

ShowMeObject.ShowMe;

finally

if ObjectAssigned then begin

ShowMeObject := nil; // Calls VCLComObject._Release.

VCLComObject := nil; // Now we can safely

// reset VCLComObject.

end;

end;

end;

end;

Q 在应用程序中广播信息

VCL 使用TWinControl 的Broadcast 方法通知应用程序中所有的类。一个控制必须解释一个事件句柄来对消息作出反应,如果你希望停止这条消息,让Message.Result 返回0 即可。

var i: integer;

hMessage: TMessage;

begin

hMessage.Msg := WM_USER + 1;

hMessage.WParam := 0;

hMessage.LParam := 0;

for i := 0 to Screen.FormCount-1 do

Screen.Forms[i].Broadcast(hMessage);

end;

TScreen 类拥有应用程序中所有的窗体。事件句柄如下:

TMyButton = class(TButton)

protected

procedure EventHandler(var Message: TMessage);

message WM_USER + 1;

end;

..

procedure TMyButton.EventHandler(var Message: TMessage);

begin

// commands

Message.Result := 0; // Event continues

end;

R 千年虫问题

在SysUtils 单元有一个全局字型(Word )变量TwoDigitYearCenturyValue ,默认值为0 ,可以解决某些Y2K 问题。它是如何起作用呢?

如果今天的日期是10/22/1998, 减去TwoDigitYearCenturyWindow 的值,即:1998 - 50 = 1948, 在日期区间48-98 之间的任何日期将具有第一个日期的世纪数,即10/22/1998 中的\'19\' 。

TwoDigitYearCenturyWindow 决定将字符串日期中的两位数年份转换为数值日期时使用的世纪数。在提取世纪之前,用当前年份减去这个值。这种方法可以延长使用两位数记年的软件的寿命。其实,对Y2K 问题的最好解决办法是不接受两位数记年法,在输入日期时要求4 位数,以排除世纪的混淆。下表列出了使用TwoDigitYearCenturyWindow 进行世纪数转换的方法。

当前年份

TwoDigitCenturyWindow 取值

世纪的起始

StrToDate 函数值

\'1/01/03\'

\'1/01/68\'

\'1/01/50\'

1998

0 ( 默认)

1900

1903

1968

1950

2002

0 ( 默认)

2000

2003

2068

2050

1998

50

1948

2003

1968

1950

2002

50

1952

2003

1968

2050

S 动态保存窗体控制

想动态地将一个窗体上所有控制的值保存到一个文件吗?利用初始化文件(ini 文件)和RTTI ,只需要编写一次代码,无论窗体上有多少控制,都可以将它们的值写到指定的文件。同样,写一个逆过程,可以将保存在文件的值直接读出,并设置到对应的控制中。

var

sSection, sFileName, sIdent: String;

iniMyFile: TiniFile;

begin

try

iniMyFile := TIniFile.Create(sFileName);

for i := 0 to (Self.ComponentCount - 1) do

begin

sSection := Self.Name;

if Components[i] is TEdit then

begin

with Components[i] as TEdit do

begin

sIdent := Name;

if Trim(Text) = \'\' then

iniMyFile.WriteString(sSection, sIdent, \'0\')

else

iniMyFile.WriteString(sSection, sIdent, Text);

end;

end

else if Components[i] is TCheckBox then

begin

sIdent := Components[i].Name;

iniMyFile.WriteBool(sSection, sIdent, (Components[i] as

TCheckBox).Checked);

end

else if { 下面编写窗体上使用过的所有的控制类型 }

begin

end;

end; { for i }

finally

iniMyFile.Free;

end; { try..finally }

end; { End of SaveToFile }

结果文件类似于:

[ 窗体名称]

edit_ucost_c_3=61.80

edit_ucost_c_5=66.30

edit_ucost_c_6=69.90

edit_ucost_c_7=64.20

edit_ucost_c_8=66.30

cbShowUtilization=1

T 使用长文件名的方法

现在,使用含有空格的长文件名是十分普遍的,使用ParamStr(1) 函数不能正确地得到这样的长文件名。例如,你通过命令行参数传递“ABC Company, Inc.Dat” 时,ParamStr(1) 只返回ABC ,这并不是你希望的长文件名。这是因为ParamStr(1) 在遇到第一个空格时就停止了。解决办法是使用下面的LoadCmdLineFile 过程:

Uses

System, SysUtils;

Procedure LoadCmdLineFile;

Var

sCmdLine: String;

I: Integer;

Begin

If ParamCount > 0 Then Begin

sCmdLine := \'\';

{ 调用ParamCount 次ParamStr 函数,每次加上一个尾随空格,

确保正确处理带有空格的长文件名 }

For I := 1 To ParamCount Do

sCmdLine := sCmdLine + ParamStr(I) + \' \';

{ 截取尾随的空格}

sCmdLine := TrimRight(sCmdLine);

{ 在打开文件之前保证文件存在}

If FileExists(sCmdLine) Then Begin

{ 添加打开文件的代码}

End;

End;

End;

U 使窗体的部分内容可见

为使窗体的部分内容可见,将窗体的有关部分放置在一个分离的TPanel 组件上,将这个面板组件的Visible 属性设置为假,记住改变窗体的大小。

例如, 如将一个辅助部分放在Panel1 组件上,它在窗体的底部, 并且你想让用户通过按Button1 按钮来显示或隐藏它,代码如下:

procedure TForm1.Button1Click(Sender:TObject);

begin

if Panel1.Visible then begin

Panel1.Visible := false;

Height := Height - Panel1.Height;

Button1.Caption := "Show More Options";

end

else begin

Panel1.Visible := true;

Height := Height + Panel1.Height;

Button1.Caption := "Hide More Options";

end;

end;

V 使EXE 程序只运行一次

下面的代码确保EXE 程序只运行一次:

procedure TForm1.FormCreate(Sender: TObject);

begin

{ 搜索数据库看程序是否运行}

if GlobalFindAtom(\'PROGRAM_RUNNING\') = 0 then

{ 假如没有找到,就添加到数据库}

atom := GlobalAddAtom(\'PROGRAM_RUNNING\')

else begin

{ 如果程序已经运行,显示信息并退出程序 }

MessageDlg(\' 程序已经运行!\', mtWarning, [mbOK], 0);

Halt;

end;

end;

procedure TForm1.FormDestroy(Sender: TObject);

begin

{ 退出程序时,从数据表中删除添加的条目 }

GlobalDeleteAtom(atom);

end;

七、小结

在Internet 的信息海洋中,还有无数的宝藏等待我们去发现、开发,作者只是在畅游Internet 时,偶有心得发现,觉着有必要与天下有识之士共享发现宝藏的快乐,才作成此文。

其实,在Internet 的各个角落里都蕴涵真正的赤金,请发现者能够将自己的心得发现公布与众,给大家提供一条通往知识宝库的钥匙。