DELPHI 编程技巧集锦( 1 )
董占山
( 中国农科院棉花研究所,河南安阳, 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 库例程的源代码:
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}
有时,需要在一个项目的多个单元中使用同一个条件定义,可以采用下面的方法:
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 的各个角落里都蕴涵真正的赤金,请发现者能够将自己的心得发现公布与众,给大家提供一条通往知识宝库的钥匙。