|
作者: liuluo [liuluo] 论坛用户 | 登录 |
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库例程的源代码: 3 按下<Ctrl>键; 3 将鼠标光标移到想看其源码的类型声明的名字上; 3 单击之即可调出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} 有时,需要在一个项目的多个单元中使用同一个条件定义,可以采用下面的方法: 4 单击"Project"*"Options",弹出Project Options对话窗口; 5 单击"Directories/Conditionals"标签; 6 在"Conditional Defines"编辑框中输入MYDEF1; 7 单击"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的各个角落里都蕴涵真正的赤金,请发现者能够将自己的心得发现公布与众,给大家提供一条通往知识宝库的钥匙。 |
地主 发表时间: 04-06-13 01:24 |
回复: ziaichen [ziaichen] 论坛用户 | 登录 |
好东东 收藏先 |
B1层 发表时间: 04-06-13 14:54 |
回复: kailangq [kailangq] 版主 | 登录 |
看了一半...头晕,太长的,留着下次看... |
B2层 发表时间: 04-06-13 15:43 |
回复: liuluo [liuluo] 论坛用户 | 登录 |
这是我光盘中的一个东东 看了觉得好 于是传上来给大家分享啊 |
B3层 发表时间: 04-06-14 20:45 |
回复: Angel [cike] 论坛用户 | 登录 |
没看 但是要顶 |
B4层 发表时间: 04-07-23 11:36 |
|
20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon
粤ICP备05087286号