· 日月光华 精华区文章阅读
发信人: Delphii (Delphi), 信区: VCL
标 题: Delphi编码规则
发信站: 日月光华站 (Fri Sep 7 12:03:07 2001) , 站内信件
Delphi代码编写标准指南
■■■■■■■■■■■■■■■■■■■■■■■■
目录
■■■■■■■■■■■■■■■■■■■■■■■■
一、序言
二、通用源代码格式规则
2.1 缩格
2.2 页边空格
2.3 Begin…End 配对
三、Obje ct Pascal
3.1 括号
3.2 保留字和关键字
3.3 过程和函数(例程)
3.3.1 命名/格式化
3.3.2 形式参数
3.3.2.1 格式化
3.3.2.2 命名
3.3.2.3 参数的排序
3.3.2.4 常量参数
3.3.2.5 名称的冲突
3.4 变量
3.4.1 变量的命名和格式
3.4.2 局部变量
3.4.3 全局变量的使用
3.5 类型
3.5.1 大写约定
3.5.1.1 浮点指针类型
3.5.1.2 枚举类型
3.5.1.3 变数和ole变数类型
3.5.2 结构类型
3.5.2.1 数组类型
3.5.2.2 记录类型
3.6 语句
3.6.1 if 语句
3.6.2 case 语句
3.6.2.1 一般性话题
3.6.2.2 格式
3.6.3 while 语句
3.6.4 for 语句
3.6.5 repeat 语句
3.6.6 with 语句
3.6.6.1 一般话题
3.6.6.2 格式
3.7 结构异常处理
3.7.1 一般话题
3.7.2 try…finally的使用
3.7.3 try…except的使用
3.7.4 try…except…else的使用
3.8 类类型
3.8.1 命名和格式
3.8.2 域
3.8.2.1 命名/格式
3.8.2.2 可视化
3.8.3 方法
3.8.3.1 命名/格式
3.8.3.2 使用静态的方法
3.8.3.3 使用虚拟/动态的方法
3.8.3.4 使用抽象的方法
3.8.3.5 属性存取方法
3.8.4 属性
3.8.4.1 命名/格式
3.8.4.2 使用存取的方法
四、文件
4.1 工程文件
4.1.1 命名
4.2 窗体文件
4.2.1 命名
4.3 数据模板文件
4.3.1 命名
4.4 远端数据模板文件
4.4.1 命名
4.5 Unit文件
4.5.1 通用Unit结构
4.5.1.1 unit的名字
4.5.1.2 uses子句
4.5.1.3 interface部分
4.5.1.4 implementation部分
4.5.1.5 initialization部分
4.5.1.6 finalization部分
4.5.2 窗体单元
4.5.2.1 命名
4.5.3 数据模板单元
4.5.3.1 命名
4.5.4 一般目的单元
4.5.4.1 命名
4.5.5 构件单元
4.5.5.1 命名
4.6 文件头
五、窗体和数据模板
5.1 窗体
5.1.1 窗体类型命名标准
5.1.2 窗体实例命名标准
5.1.3 自动创建窗体
5.1.4 模式窗体实例化函数
5.2 数据模板
5.2.1 数据模板命名标准
5.2.2 数据模板实例命名标准
六、包
6.1 使用运行包和设计包的比较
6.2 文件命名标准
七、构件
7.1 用户自定义构件
7.2 构件单元
7.3 使用注册单元
7.4 构件实例命名约定
7.5 构件的前缀
7.6 Standard页
7.7 Additional页
7.8 Win32页
7.9 System页
7.10 Internet页
7.11 Data Access页
7.12 Data Controls页
7.13 Decision Cube页
7.14 QReport页
7.15 Dialogs页
7.16 Win3.1页
7.17 Samples页
7.18 ActiveX页
7.19 Midas页
■■■■■■■■■■■■■■■■■■■■■■■■
一、序言
■■■■■■■■■■■■■■■■■■■■■■■■
本文档详述了在Delphi 4开发者指南下进行编程的代码编写标准。在通常情况下,
本文档遵循“取消”式格式的指引方针,该方针由Borland国际通过一些例外来使用。在
Delphi 4开发者指南中包含本文档的目的在于阐述一种方法,通过该方法,开发小组可
以在他们所编写的代码中保持一贯的风格。这样做的目的是使在开发小组中的每一个程
序员都可以明白其他程序员的代码。这有助于提高代码编写的可读性和使用的一贯性。
本文档并不意味着包含了所有存在于代码中的标准。但是,它的内容已足够帮你起
个好头。你可以自由的增加修改这些标准来满足你的需要。我们不赞成你偏离这些由Bo
rland开发人员所使用的标准太远。我们推荐这么做是因为一旦有新的程序员加入到你的
开发小组中,而他们最喜欢和最熟悉的是Borland的标准。象大多数代码标准文档,本文
档也会根据需要进行改动。因此,你可以到www.xapware.com/ddg中找到最新的更新版本
。本文档不包括用户接口标准。本文档是独立的但也是同样重要的。已经有足够的第三
方书籍和Microsoft文档包括了另外一些指导方针,而我们决定并不复制这些信息,但我
们会指引你到Microsoft Developers Network 和一些资源,在那儿可以找到你所需的信
息。
■■■■■■■■■■■■■■■■■■■■■■■■
二、通用源代码格式规则
■■■■■■■■■■■■■■■■■■■■■■■■
2.1 缩格
缩格是指在每一级有两个空格。不要在源代码中保留tab字符,这是因为tab字符会
随着不同用户的不同设置和不同的资源管理工具(打印、文档、版本控制等)而代表不
同的宽度。
你可以通过关闭Environment选项对话框中Editor页上的“Use tab character”和
“Optimal fill”检查框(通过ools|Environment)来禁止保存tab字符。
2.2 页边空格
页边空格会被设置成80字符宽。通常,源码不会超出这个边界,但这个方针会有一
些弹性。不管是否有可能,那些超出到另一行的语句会在一个逗号或其他操作符之后与
前面的语句相连。当一个语句被打断相连时,它应比原来的那一行语句缩进两个字符。
2.3 Begin…End 配对
Begin 子句应写在独立的一行。例如,下面第一行是错误的写法而第二行是正确的
。
for I := 0 to 10 do begin //错误,begin同for在同一行
for I := 0 to 10 do //正确,begin出现在独立的一行
begin
这个规则的例外是当begin子句的出现是作为一个else子句的一部分,参考例子:
if some statement then
begin
...
end
else begin
someOtherStatement;
end;
end 语句永远出现在独立的一行。
当begin语句不是一个else子句的一部分时,相应的end语句永远缩进到与begin部分
相对应的位置。
■■■■■■■■■■■■■■■■■■■■■■■■
三、Object Pascal
■■■■■■■■■■■■■■■■■■■■■■■■
3.1 括号
永远不要在括号与括号之间的字符中间留下空格。下面的例子示范了错误的与正确
地使用括号中的空格:
CallProc( Aparameter ); //错误
CallProc(Aparameter); //正确
永远不要在一个语句中使用不必要的括号。括号只应在源代码中需要的地方使用。
以下的例子示范了错误和正确的使用:
if (I = 42) then //错误 - 多余的括号
if (I = 42) or (J = 42) then //正确 - 需要括号
3.2 保留字和关键字
Object Pascal 保留字和关键字永远是全部小写。
3.3 过程和函数(例程)
3.3.1 命名/格式化
例程的名字永远应该以大写的字母开头并且中间错落分明以便于可读性。下面是一
个不正确格式的过程名称:
procedure thisisapoorlyformattedroutinename;
下面是一个合适的大小写例程名称的例子:
procedure ThisIsMuchMoreReadableRoutineName;
例程的名称应该同它的内容相符。一个会导致某个行为的例程应以动词开头。例如
:
procedure FormatHardDrive;
一个用于设置输入参数的例程应以单词set作为前缀,例如:
procedure SetUserName;
一个用来接收某个值的例程应以单词get作为前缀,例如:
procedure GetUserName : string;
3.3.2 形式参数
3.3.2.1 格式化
如果有的话,相同类型的形参应合并在一个语句中:
procedure Foo(Param1, Param2, Param3 : Integer; Param4 : string);
3.3.2.2 命名
所有形参的名字应是十分符合它们所代表的意义,特别是应该以传送到例程中的标
志符的名称为基础。一个好的参数名称应以字符A为前缀 - 例如:
procedure SomeProc(AuserName : string; AuserAge : integer);
“A”前缀按约定表示该参数的名称是与类类型中的一个属性或域的名称相对应的。
3.3.2.3 参数的排序
下面的形参的顺序重点说明了注册者调用约定调用的好处。
- 最常用的参数应放在第一位,其它的参数应按从左到右的顺序排列。
- 输入参数列表应放在输出参数列表的左边。
- 将通用的参数放在特殊参数的左边,例如:
procedure SomeProc(Aplanet, AContinent, Acountry, Astate, Acity)
- 排序有可能有些例外,比如事件的处理。类型为TObject的Sender参数经常放在第一
位。
3.3.2.4 常量参数
当一个参数为记录型、数组类型、ShortString、或接口类型并且在例程中不被改变
时,这些参数应做上常量标记。这样做会让编译器更加有效率的产生有关这些不改变的
参数的代码。
而例程中另外一些非变参数也可常量来传送。尽管这样做没有产生任何效果和提高
效率,这将会给调用例程的使用者提供更多的信息。
3.3.2.5 名称的冲突
当使用拥有两个名称相同的例程的两个单元时,如果你调用该例程时,在uses子句
中排在后面的单元中的例程将会被调用。为了解决这种“在uses子句上的模糊”冲突,
要在调用该例程时写上相关的单元的前缀,例如:
sysUtile.FindClose(SR);
或
windows.FindClose(Handle);
3.4 变量
3.4.1 变量的命名和格式
变量的命名应以使用它们的目的相符
循环控制变量应采用一个单独的字符作为名字,比如 I,J,或K,也可以采用更加
有意义的名字,比如 UserIndex。
逻辑变量的名字应能充分表达准确的真或假的意思。
3.4.2 局部变量
一个过程中的局部变量应遵循所有其它变量的使用和命名约定。临时变量的取名应
合理。
如果必须的话,在一进入例程就应初始化局部变量。局部的AnsiString变量会自动
初始化为一个空的字符串。
局部接口和派分接口类型变量将会自动初始化为nil,并且局部变数和ole变数类型
变量会自动初始化为Unassigned
3.4.3 全局变量的使用
使用全局变量是不推荐的。但是,在某些时候还是必须使用,而且它们也只应在必
须使用的时候才使用。在这种时候,你应努力只在一段上下文范围内使用全局变量。例
如,一个全局变量只应在一个单元的implemntation部分内是全局的。如果打算在多个单
元类使用全局数据,你应将它们移到一个公共的单元中然后被其它所有单元使用。
全局变量可以在var子句中直接初始化为一个值。记住,所有的全局数据会自动初始
化为0,因此不要将全局变量初始化为一个“空”值比如 0、nil、’’、Unassigned、等
等。这样做的一个理由是因为零-初始化的全局数据在exe文件中不会占据任何空间。零
-初始化数据被存储在一个虚拟的数据段,它在应用程序启动后被分配在一段内存中。
非零-初始化的全局数据在硬盘的exe文件占用空间。
3.5 类型
3.5.1 大写约定
如果类型的名字是保留字,那么它应全部小写。Win32 API类型通常全部大写,并且
你必须遵循在Windows.pas或其他API单元中的详细类型名称的约定。对于其他变量名字
,地一个字母应为大写,而其他字母应错落有致。下面是一些例子:
var
MyString : string; //保留字
WindowHandle : HWND; //Win32 API 类型
I : Integer; //在System单元中引进的类型标识符
3.5.1.1 浮点指针类型
不推荐使用Real类型,因为它的存在只是为了向前兼容早期的Pascal代码。在通常
情况下用Double来实现浮点指针的需要。并且,Double对处理器和总线而言是做了最优
化处理的,它也是IEEE中定义的标准数据格式。只有当需要的范围超出Double所定义的
范围时才使用Extended。Extended是intel定义的类型且在Java中不支持。只有当浮点指
针变量的实际字节大小有其意义时才使用Single。(比如当使用另一种语言的DLLs时)
。
3.5.1.2 枚举类型
枚举类型的名字需符合使用该类型的目的。该类型的名字需以字符T为前缀,以表明
这是一个类型。枚举类型中的标识符列表必须包含两个或三个字符的前缀来对应于该枚
举类型的名字 - 例如:
TsongType = (stRock, stClassical, stCountry, stAlternative, stHeavyM
etal, stRB);
一个枚举类型的实例的名字应与不要前缀的枚举类型(SongType)相同,除非有更
好的原因来赋予该变量更特殊的名字,比如:FavoriteSongType1,FavoriteSongType2
等等。
3.5.1.3 变数和ole变数类型
通常不建议使用变数和Ole变数类型。但在只有运行时刻才能知道数据类型的程序中
必须使用该类型,这种情形多出现在COM和数据库开发中。Ole变数使用在以COM为基础的
编程中例如自动化和ActiveX控制,而变数使用在非COM的编程中,这是因为变数可以十
分有效地存储本地Delphi字符串(同一个字符串变量一样),但Ole变数会将所有的字符
串转换为Ole字符串(WideChar 字符串)并且并不实例运算 - 它们永远拷贝。
3.5.2 结构类型
3.5.2.1 数组类型
数组类型的名字需符合它们使用的目的。该类型的名字必须加以前缀T。如果须声明
该数组类型的指针,那么该指针需加以前缀P而且应立即声明在该数组声明的前面。例如
:
type
PCycleArray = ^TCycleArray;
TCycleArray = array[1…100] of integer;
在实际应用中,数组的变量实例的名称应是其类型的名字去掉前缀T。
3.5.2.2 记录类型
记录类型的名字应符合使用它们的目的。其类型的声明应加以前缀T。如果要声明该
记录类型的指针,就应加以前缀P并且应紧靠在类型声明的前面声明。例如:
type
PEmployee = ^TEmployee;
TEmployee = record
EmployeeName : string;
EmployeeRate : Double;
end;
3.6 语句
3.6.1 if 语句
在if/then/else语句中最常发生的行为应放在then子句中,而其它发生可能性较小
的行为应放在else子句中。
尽量避免使用嵌套的if语句,在这种情形下应用多个if语句来判断各种可能。
不要使用if嵌套超过五级深度。应使代码编写得更加清晰、明了。
不要在if语句中使用不必要的圆括号。
如果在if语句中有多个条件需测试,这些条件应按计算强度由少到多的顺序从左到
右排列。这样做能使编译器在编译代码时获得布尔估算逻辑的捷径,从而使你的代码获
得最佳的优化。举例来说,如果条件1快过条件2,而条件2快过条件3,那么在if语句中
的排列应是:
if 条件1 and 条件2 and 条件3 then
3.6.2 case 语句
3.6.2.1 一般性话题
在一个case语句中的各个独立的单元应以数字或字母顺序排列。
每一个case单元的动作行为应保持简单而不应该超过四到五行代码。如果所要执行
的动作过于复杂应采用独立的过程或函数。
Case语句中的else子句只有当需要缺省行为或处理错误时才使用。
3.6.2.2 格式
case语句应遵循其它结构的缩格和命名约定。
3.6.3 while 语句
在一个while语句中不建议使用exit过程来跳出循环,尽量仅使用循环条件来跳出循
环。
在一个while循环中所用的初始化代码应紧靠在进入while循环前面出现而不要被其
它不相关的语句隔开。
任何结束后的处理应在循环之后立即进行。
3.6.4 for 语句
for语句只有当循环次数已知的情况下才能取代while语句使用。
3.6.5 repeat 语句
repeat语句的使用同while语句一样,并且遵循同样的通用方针。
3.6.6 with 语句
3.6.6.1 一般话题
with语句应节省使用,并且带有大量的警告。避免过度使用with语句并且在with语
句中小心使用多个对象、记录等等。例如:
with Record1, Record2 do
这些事情会使程序员感到困惑并难以发现问题所在。
3.6.6.2 格式
with 语句遵循本文档所说明的命名约定和缩格的格式规则。
3.7 结构异常处理
3.7.1 一般话题
异常的处理大量地使用在错误纠正和资源保护方面。这就是说一旦资源被分配,一
个try…finally必需加以使用来保证该资源被正确的释放。这种异常的保护也是指在一
个单元的initializition/finalization或一个对象的constructor/destructor中进行资
源的分配和释放。
3.7.2 try…finally的使用
任何情形下,每一次的分配都应跟随一个try…finally。举例来说,下面的代码会
造成可能的错误:
SomeClass1 := TsomeClass.Create;
SomeClass2 ;= TsomeClass.Create;
try
{ do some code }
finally
SomeClass1.Free;
SomeClass2.Free;
end;
一个更安全更合适的分配过程应是:
SomeClass1 := TSomeClass.Create;
try
SomeClass2 := TsomeClass.Create;
try
{ do some code }
finally
SomeClass2.Free;
end;
finally
SomeClass1.Free;
end;
3.7.3 try…except的使用
只有当在异常被触发而你想执行一些任务时才使用try…except。通常,你没有必要
为了只是简单地在屏幕上显示一个错误信息而使用try…except语句,因为这会被Appli
cation对象自动执行。如果你想在except子句中执行完一些任务之后调用缺省的异常处
理,使用raise来重新触发异常到下一个句柄。
3.7.4 try…except…else的使用
try…except中的else子句不建议使用,因为它会打断所有的异常包括那些你没有准
备的异常。
3.8 类类型
3.8.1 命名和格式
类类型的名称应符合使用它们的目的。类型名字应加以前缀T以表明这是一个类型的
定义 - 例如:
type
Tcustomer = class(TObject)
类型的实例通常是没有前缀T的类型的名字 - 例如:
var
Customer :Tcustomer;
注意:查阅“构件类型的命名标准”来获得更多有关构件命名的信息。
3.8.2 域
3.8.2.1 命名/格式
类的域名遵循与变量标识符同样的约定除了它们应以F为前缀,来表明这是一个域的
名称。
3.8.2.2 可视化
所有的域都必需是私有的。想在类的范围之外存取域得通过属性来使用。
3.8.3 方法
3.8.3.1 命名/格式
方法的命名应遵循本文档中有关过程和函数的约定叙述。
3.8.3.2 使用静态的方法
如果使用一个静态的方法,那么该方法就不能被该类的后代类所继承。
3.8.3.3 使用虚拟/动态的方法
如果你打算该类的方法能被后代的类所继承就得使用虚拟的方法。只有在该方法有
多个继承时(直接的或间接的)才使用动态的方法。例如,一个类类型包含一个可继承
的方法,而100个后代类要继承这种方法,那么这个方法就会动态地产生为100个后代类
使用的内存。
3.8.3.4 使用抽象的方法
如果在一个类中使用抽象的方法,该类就不能被创建。只有在那些永远不会被创建
的类中使用抽象的方法。
3.8.3.5 属性存取方法
所有存取类的方法都只能出现在类的private或protected部分。属性存取方法的命
名应遵循过程和函数的约定规则。读取存取方法(方法读取器)必需以单词Get为前缀。
写入存取方法(方法写入器)必需以单词Set为前缀。方法写入器的参数的名字应为Val
ue,并且它的类型应是它所操作的属性的类型。例如:
TSomeClass = class(TObject)
private
FsomeField : Integer;
protected
function GetSomeField : Integer;
procedure SetSomeField(Value : Integer);
public
property SomeField : Integer read GetSomeField write SetSomeField;
end;
3.8.4 属性
3.8.4.1 命名/格式
属性如果是表示为一个私有域的存取器的话,那么它的名字应是它们所操作的域的
名字除去解释符F。
属性的名字应是名词,不是动词。属性表示的是数据,而方法表示的是行为。
数组类型的名称应为复数。一般情况下属性的名称应为单数。
3.8.4.2 使用存取的方法
尽管没有要求,但还是建议尽量少地为一个表示私有域的属性而使用写入存取方法
。
■■■■■■■■■■■■■■■■■■■■■■■■
四、文件
■■■■■■■■■■■■■■■■■■■■■■■■
4.1 工程文件
4.1.1 命名
工程文件应取个描述性的名字。例如,Delphi 4开发者指南错误管理器 的工程名字
是:DDGBugs.dpr。一个有关系统信息的程序的名字就应象 SysInfo.dpr。
4.2 窗体文件
4.2.1 命名
一个窗体文件的取名应可以描述使用该窗体的目的,并加以后缀Frm。例如,一个“
关于”的窗体的文件名应是AboutFrm.dpr。主窗体的文件名应是MainFrm.dpr。
4.3 数据模板文件
4.3.1 命名
数据模板的取名应能表示使用该数据模板的目的,它的名称应加以两个字符的后缀
DM。例如,自定义数据模板的文件名字应为CustomersDM.dfm。
4.4 远端数据模板文件
4.4.1 命名
远端数据模板的取名应能表示使用该远端数据模板的目的,它的名称应加以三个字
符的后缀RDM。例如,自定义远端数据模板的文件名字应为CustomersRDM.dfm。
4.5 Unit文件
4.5.1 通用Unit结构
4.5.1.1 unit的名字
Unit文件应取一个可描述性的名字。例如,包含应用程序主窗体的单元应叫做Main
Frm.pas。
4.5.1.2 uses子句
在interface部分的uses子句应包含在interface部分中的代码所需要的单元。去掉
那些Delphi可以自动加入到程序中的单元。
在implementation部分的uses子句应只包含在implementation部分中的代码所需要
的单元的名字。去掉不必要的单元。
4.5.1.3 interface部分
interface部分应包含只那些其它单元所需要存取类型的定义、变量、过程/函数的
预定义等等。否则,就应放在implementation部分定义。
4.5.1.4 implementation部分
implementation部分应包含那些只在本单元中私用的类型定义、变量、过程/函数定
义等等。
4.5.1.5 initialization部分
不要在initialization 部分放入耗时长的代码,这将使程序的第一个界面出现得比
较缓慢。
4.5.1.6 finalization部分
在这里要保证释放你在Initialization部分所分配的任何资源。
4.5.2 窗体单元
4.5.2.1 命名
一个窗体的单元文件应拥有与它所对应的窗体文件同样的名称。例如,“关于”窗
体的单元名称应为 AboutFrm.pas,而主窗体的单元名称应为MainFrm.pas。
4.5.3 数据模板单元
4.5.3.1 命名
一个数据模板的单元文件应拥有与它所对应的数据模板文件同样的名称。例如,一
个自定义数据模板单元的名称应为CustomersDM.pas。
4.5.4 一般目的单元
4.5.4.1 命名
一般目的单元的取名应符合使用该单元的目的。例如,一个实用程序单元取名为Bu
gUtilities.pas。一个包含全局变量的单元取名为CustomerGlobals.pas。
注意,该单元的名字不能与它的工程中所使用的所有包中的单元的名字相同。不赞
成使用一般的或通用的单元名字。
4.5.5 构件单元
4.5.5.1 命名
构件单元应放在独立的目录,以将它们同定义构件组或构件集合的单元区分开来。
它们要永远同工程在不同的目录。单元名字应同它们的内容相符。
注意:查阅“用户定义的构件”部分来获得更多有关构件命名标准的信息。
4.6 文件头
建议在所有源文件、工程文件、单元等等中使用信息化文件头。一个良好的文件头
应包含以下信息:
{
版权… 著作的年、月、日…
}
■■■■■■■■■■■■■■■■■■■■■■■■
五、窗体和数据模板
■■■■■■■■■■■■■■■■■■■■■■■■
5.1 窗体
5.1.1 窗体类型命名标准
窗体类型的取名应能表达使用该窗体的目的。类型定义应加以前缀T。前缀后面跟随
着描述性的名字。最后,应加以Form后缀来描述名字。例如,一个“关于”的窗体的类
型的名字应为:
TAboutFrom = class(TForm);
主窗体的定义为:
TMainForm = class(TForm);
一个用户接入窗体的名字应象:
TCustomerEntryForm = class(TForm);
5.1.2 窗体实例命名标准
窗体实例应是没有带前缀T的相应类的名字。例如,对应于前面窗体类型而言,其实
例的名字应为:
类型名称 实例名称
TAboutForm AboutForm
TMainForm MainForm
TCustomerEntryForm CustomerEntryForm
5.1.3 自动创建窗体
只有主窗体可以是自动创建的除非有其它更好的理由不这样做。所有其它的窗体必
需从工程选项对话框中的自动创建列表中移走。查阅以下部分来获得更多的信息。
5.1.4 模式窗体实例化函数
所有的窗体单元都应包含一个窗体实例化函数,该函数用来创建、设置、模式地显
示窗体,并释放窗体。该函数应返回窗体的模式结果。该函数要传递的参数应遵循本文
档指定的“参数传递”标准。通过这种方式封装的函数性有助于代码的再利用和维护。
该窗体的变量要从单元中移走,并再窗体实例的函数中进行本地式地定义。注意,
这就意味着该窗体必需从工程/选项对话框中的自动创建列表中剔除。参考本文档后面的
“自动创建窗体”。
例如,下面的单元展示了再GetUserData窗体中的一个函数。
unit UserDataFrm;
interface
uses windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dial
ogs, StdCtrls;
type
TUserDataForm = class(TForm)
edtUserName : TEdit;
edtUserID : TEdit;
private
{ Private declarations }
public
{ Public declarations }
end;
function GetUserData(var aUserName : String; var aUserID : Integer): Word
;
implementation
{$R *.DFM }
function GetUserData(var aUserName : String; var aUserID : Integer): word;
var
UserDataForm : TuserDataForm;
begin
UserDataForm := TuserDataForm.Create(Application);
try
UserDataForm.Caption := ‘Getting User Data’;
Result := UserDataForm.ShowModal;
if (Result = mrOK) then
begin
aUserName := UserDataForm.edtUserName.Text;
aUserID := StrToInt(UserDataForm.edtUserID.Text);
end;
finally
UserDataForm.Free;
end;
end;
end.
5.2 数据模板
5.2.1 数据模板命名标准
数据模板的取名要符合使用该数据模板的目的。类型的定义应加以前缀T,后面紧接
着描述性的名字,最后要加以后缀单词“DataModule”。例如,一个自定义的数据模板
有时候应该象:
TCustomerDataModule = class(TDataModule)
一个命令式的数据模板的名字应象:
TOrdersDataModule = class(TDataModule)
5.2.2 数据模板实例命名标准
数据模板实例的名称应是对应不带前缀T的类型的名称。例如,对于前面的窗体类型
而言,其实例的名称应为:
类型名称 实例名称
TCustomerDataModule CustomerDataModule
TOrdersDataModule OrdersDataModule
■■■■■■■■■■■■■■■■■■■■■■■■
六、包
■■■■■■■■■■■■■■■■■■■■■■■■
6.1 使用运行包和设计包的比较
运行时刻的包应只包含其它构件包所要求的单元或构件。另外,包含属性/构件编辑
器和其它只为设计的代码应放入到设计时刻包中。注册单元应放在设计包中。
6.2 文件命名标准
包的名称应依照下面的例子:
“iiilibvv.pkg” - 设计时刻包
“iiistdvv.pkg” - 运行时刻包
字符“iii”表示一个3字符标识前缀。这个前缀用来表明公司、个人或其它有标识
意义的实体。
字符“vv”表示为该包想要对应Delphi某个版本的包的版本号。
注意,包的名字中包含“lib”或“std”的意思是表明这是一个设计时刻包还是一
个运行时刻包。
如果既是设计时刻包又是运行时刻包,该文件的命名是同上面一样的,例如,为De
lphi 4开发者指南做的包的名称应为:
DdgLib40.pkg – 设计时刻包
DdgStd40.pkg – 运行时刻包
■■■■■■■■■■■■■■■■■■■■■■■■
七、构件
■■■■■■■■■■■■■■■■■■■■■■■■
7.1 用户自定义构件
在标准构件中命名出来的构件的名称同在“类类型”部分定义中的一样定义成一个
类类型,不同的是它们有一个3字符的指示前缀,这个前缀可以表示公司、个人或其它实
体。例如,一个为Delphi 4开发者指南编写的时钟构件的名称定义为:
TddgClock = class(TComponent)
注意,那三个前缀字符是小写的。
7.2 构件单元
构件单元应只包含一个主要的构件,一个主要的构件是指出现在构件栏中的构件。
主要构件的辅助构件/对象应放入到同一个单元中。
7.3 使用注册单元
构件的注册过程应从构件本身的单元中剔除,并放入到一个独立的单元中。这个注
册单元可以用来注册任何构件、属性编辑器、构件编辑器、专家器等。
构件的注册只应在设计时刻包中进行,注册单元应包含在设计时刻包中而不应放在
运行时刻包中。
推荐使用的注册单元的名称是:
XxxReg.pas
上面的3个前缀字符“Xxx”用来表示一个公司、个人或任何其它的实体。例如,在
Delphi 4 开发者指南中的注册单元的名称应为 DdgReg.pas。
7.4 构件实例命名约定
所有的构件都应取个描述性的名称。由Delphi创建的缺省名的构件不会被遗弃。在
设计构件类型时应设计一个小写的前缀。使用前缀而不使用后缀的原因是在搜寻时,在
对象检查器和代码探索器中搜寻构件的名字比搜寻构件的类型更容易实现。
7.5 构件的前缀
以下构件的前缀可以用来设计Delphi 4中的标准构件。请在此列表中加入第三方构
件的前缀。
7.6 Standard页
前缀 构件
mm TMainMenu
pm TPopupMenu
mmi TMainMenuItem
pmi TPopupMenuItem
lbl TLabel
edt TEdit
mem TMemo
btn TButton
cb TCheckBox
rb TRadioBox
lb TListBox
cb TComboBox
scb TScrollBar
gb TGroupBox
rg TRadioGroup
pnl TPanel
cl TCommandList
7.7 Additional页
前缀 构件
bbtn TBitBtn
sb TSpeedButton
me TMaskEdit
sg TStringGrid
dg TDrawGrid
img TImage
shp TShape
bvl TBevel
sbx TScrollBox
clb TCheckListbox
spl TSplitter
stx TStaticText
cht TChart
7.8 Win32页
前缀 构件
tbc TTabControl
pgc TPageControl
il TImageList
re TRichEdit
thr TTrackBar
prb TProgressBar
ud TUpDown
hk THotKey
ani TAnimate
dtp TDateTimePicker
tv TTreeView
lv TListView
hdr THeaderControl
stb TStatusBar
tlb TToolBar
clb TCoolBar
7.9 System页
前缀 构件
tm TTimer
pb TPaintBox
mp TMediaPlayer
olec TOleContainer
ddcc TDDEClientItem
ddci TDDEClientItem
ddsc TDDEServerConv
ddsi TDDEServerItem
7.10 Internet页
前缀 构件
csk TClientSocket
ssk TServerSocket
wbd TWebDispatcher
pp TPageProducer
tp TQueryTableProducer
dstp TDataSetTableProducer
nmdt TNMDayTime
nec TNMEcho
nf TNMFinger
nftp TNMFtp
nhttp TNMHttp
nMsg TNMMsg
nmsg TNMMSGServ
nntp TNMNNTP
npop TNMPop3
nuup TNMUUProcessor
smtp TNMSMTP
nst TNMStrm
nsts TNMStrmServ
ntm TNMTime
nudp TNMUdp
psk TPowerSock
ngs TNMGeneralServer
html THtml
url TNMUrl
sml TSimpleMail
7.11 Data Access页
前缀 构件
ds TDataSource
tbl TTable
qry TQuery
sp TStoredProc
db TDataBase
ssn TSession
bm TBatchMove
usql TUpdateSQL
7.12 Data Controls页
前缀 构件
dbg TDBGrid
dbn TDBNavigator
dbt TDBText
dbe TDBEdit
dbm TDBMemo
dbi TDBImage
dblb TDBListBox
dbcb TDBComboBox
dbch TDBCheckBox
dbrg TDBRadioGroup
dbll TDBLookupListBox
dblc TDBLookupComboBox
dbre TDBRichEdit
dbcg TDBCtrlGrid
dbch TDBChart
7.13 Decision Cube页
前缀 构件
dcb TDecisionCube
dcq TDecisionQuery
dcs TDecisionSource
dcp TDecisionPivot
dcg TDecisionGrid
dcgr TDecisionGraph
7.14 QReport页
前缀 构件
qr TQuickReport
qrsd TQRSubDetail
qrb TQRBand
qrcb TQRChildBand
qrg TQRGroup
qrl TQRLabel
qrt TQRText
qre TQRExpr
qrs TQRSysData
qrm TQRMemo
qrrt TQRRichText
qrdr TQRDBRichText
qrsh TQRShape
qri TQRImage
qrdi TQRDBMImage
qrcr TQRCompositeReport
qrp TQRPreview
qrch TQRChart
7.15 Dialogs页
对话框构件是由构件封装的真正的窗体,因此,它们应遵循窗体的命名约定。类型
已由构件的名称定义。它的实例的名称是Delphi自动生成的类型实例名字去掉数字后缀
。举例如下:
类型 实例名字
TOpenDialog OpenDialog
TSaveDialog SaveDialog
TOpenPictureDialog OpenPictureDialog
TSavePictureDialog SavePictureDialog
TFontDialog FontDialog
TColorDialog ColorDialog
TPrintSetupDialog PrintSetupDialog
TFindDialog FindDialog
TReplaceDialog ReplaceDialog
7.16 Win3.1页
前缀 构件
dbll TDBLookupList
dblc TDBLookupCombo
ts TTabSet
ol TOutline
tnb TTabbedNoteBook
nb TNoteBook
hdr THeader
flb TFileListBox
dlb TDirectoryListBox
dcb TDriveComboBox
fcb TFliterComboBox
7.17 Samples页
前缀 构件
gg TGauge
cg TColorGrid
spb TSpinEdit
spe TSpinEdit
dol TDirectoryOutline
cal TCalendar
ibea TIBEventAlerter
7.18 ActiveX页
前缀 构件
cfx TChartFX
vsp TVSSpell
f1b TF1Book
vtc TVTChart
grp TGraph
7.19 Midas页
前缀 构件
prv TProvider
cds TClientDataSet
qcds TQueryClientDataSet
dcom TDCOMConnection
olee TOleEnterpriseConnection
sck TSocketConnection
rms TRemoteServer
mid TMidasConnection
<完>
--
※ 来源:·日月光华站 bbs.fudan.edu.cn·[FROM: 61.170.136.227]
[返回上一页]
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
好的Delphi面向对象编程规则20条
规则一:为每一个类创建一个单元(One Class,One Unit)
请始终牢记这一点:类的私有(private)和保护(protected)的部分只对于其他单元中的类和过程(procedure)才是隐藏的.因此,如果你想得到有效的封装性,你应该为每一个类使用一个不同的单元。对于一些简单的类,比如那些继承其他类的类,你可以使用一个共享的单元。不过共享同一个单元的类的数目是受到限制的:不要在一个简单的单元里放置超过20个复杂的类,虽然Borland公司的VCL代码曾经这样做过。
如果你使用窗体的时候,Delphi会默认的遵循“一个类使用一个单元”的规则,这对于程序员来说也是十分方便的。当你向你的项目中添加一个没有窗体的类时,Delphi也会创建一个新的独立的单元。
规则二:为组件命名(Name Components)
为每一个窗体和单元给出一个有意义的名字是十分重要的。窗体和单元的名字必须是不同的,不过我趋向于为他们两者使用相似的名字,如对于关于窗体和单元可以为他们使用AboutForm 和About.pas.
为组件使用带有描述性的名字同样十分重要。最常见的命名方式是使用类的小写字母开头,再加上组件的功能,如BtnAdd 或者editName。采用这样的命名方式为组件命名可能会有很多相似的名字,而且也没有一个最好的名字,到底应该选择那一个应该依据你的个人爱好而定。
规则三:为事件命名(Name Events)
对于事件处理方法给出合适的名字更加重要。如果你对于组件给出了一个合适的名字,那么系统默认的名字ButtonClick将变成BtnAddClick。虽然从这个名字中我们可以猜到这个事件处理程序的功能,但是我认为使用一个能够描述该方法的作用的名字,而不是采用Delphi附加的名字是一种更好的方式。例如,BtnAdd按钮的OnClick事件可以命名成AddToList。这会使得你的程序可读性更强,特别是当你在这个类的其他方法中调用这个事件处理程序时,而且这会帮助程序员为类似的事件或是不同的组件选用相同的方法。不过我必须声明,使用动作(Actions)是目前开发重要的程序时我最喜欢的方法。
规则四:使用窗体方法(Use Form Methods)
窗体都是一些类,因此窗体的代码是以方法组织的。你可以向窗体中添加事件处理程序,这些处理程序完成一些特别的功能,而且他们能被其他方法调用。除了事件处理方法外,你还可以向窗体添加完成动作的特别定义的方法以及访问窗体状态的方法。在窗体中添加一些公共的(Public)方法供其他窗体调用要比其他窗体直接操作他的组件要好。
规则5:添加窗体构造器(Add Form Constructors)
在运行时创建的第二个窗体除了一个默认的构造器(从Tcomponent 类继承而来)外还会提供其他特殊的构造器。如果你不需要考虑和Delphi4以前的版本的兼容性问题,我建议你重载(Overload)Create方法,添加必要的初始化参数。具体代码可参见下面的代码:
Public
Constructor Create(Text:string): reintroduce ; overload;
Constructor TformDialog.Create(Text:string);
Begin
Inherited Create(Application);
Edit1.Text:=Text;
End;
规则6:避免全局变量(Avoid Global Variables)
应该避免使用全局变量(就是那些在单元的interface 部分定义的变量)。下面将会有一些建议帮助你如何去做。
如果你需要为窗体存储额外的数据,你可以向窗体类中添加一些私有数据。这种情况下,每一个窗体实例都会有自己的数据副本。你可以使用单元变量(在单元的implementation部分定义的变量)声明那些供窗体类的多个实例共享的数据。
如果你需要在不同类型的窗体之间共享数据,你可以把他们定义在主窗体里来实现共享,或者使用一个全局变量,使用方法或者是属性来获得数据。
规则7:永远不要在Tform1类中使用Form1(Never Use Form1 in Tform1)
你应该避免在类的方法中使用一个特定的对象名称,换句话说,你不应该在TForm1类的方法中直接使用Form1.如果你确实需要使用当前的对象,你可以使用Self关键字。请牢记:大多数时候你都没有必要直接使用当前对象的方法和数据。
如果你不遵循这条规则,当你为一个窗体类创建多个实例的时候,你会陷入麻烦当中。
规则8:尽量避免在其他的窗体中使用Form1(Seldom Use Form1 In Other Forms )
即使在其他窗体的代码中,你也应该尽量避免直接使用全局变量,如Form1.定义一些局部变量或者私有域供其他窗体使用会比直接调用全局变量要好。
例如,程序的主窗体能够为对话框定义一个私有域。很显然,如果你计划为一个派生窗体创建多个实例,这条规则将是十分有用。你可以在主窗体的代码范围内保持一份清单,也可以更简单地使用全局Sreen对象的窗体数组。
规则9:移除Form1(Remove Form1)
事实上,我的建议是在你的程序中移除Delphi自动创建的全局窗体对象。即使你禁止了窗体的自动添加功能,这也有可能是必要的,因为在Delphi随后仍然可能添加这样的窗体。我给你的建议是应该尽量避免使用全局窗体对象。
我认为对于Delphi新手而言,移除全局窗体对象是十分有用的,这样他们不至于对类和全局对象两者的关系感到疑惑。事实上,
规则10:添加窗体属性(Add Form Properties)
正如我已经提到过的,当你需要为你的窗体添加数据时,请添加一个私有域。如果你需要访问其他类的数据,可以为你的窗体添加属性。使用这种方法你就能够改变当前窗体的代码和数据(包含在它的用户界面中)而不必改变其他窗体或类的代码。
你还应该使用属性或是方法来初始化派生窗体或是对话框,或是访问他们的最终状态。正如我前文所说的,你应该使用构造器来完成初始化工作
规则11:显示组件属性(Expose Components Properties)
当你需要访问其他窗体的状态时,你不应该直接访问它的组件。因为这样会将其他窗体或其它类的代码和用户界面结合在一起,而用户界面往往是一个应用程序中最容易发生改变的部分。最好的方法是,为你需要访问的组件属性定义一个窗体属性。要实现这一点,可以通过读取组件状态的Get方法和设置组件状态的Set方法实现。
假如你现在需要改变用户界面,用另外一个组件替换现有的组件,那么你只需做的是修改与这个组件属性相关的Get方法和Set方法,而不必查找,修改所有引用这个组件的窗体和类的源码。详细实现方法请参见下面的代码:
private
function GetText:String;
procedure SetText(const Value:String);
public
property Text:String;
read GetText write SetText;
function TformDialog.GetText:String;
begin
Result:=Edit1.Text;
end;
procedure TformDialog.SetText(const Value:String);
begin
Edit1.Text;=Value;
end;
规则12:属性数组(Array Properties)
如果你需要处理窗体中的一系列变量,你可以定义一个属性数组。如果这些变量是一些对于窗体很重要的信息,你还可以把他们定义成窗体默认的属性数组,这样你就可以直接使用SpecialForm[3]来访问他们的值了。
下面的代码显示了如何将一个listbox组件的项目定义成窗体默认的属性数组。
type
TformDialog =class(TForm)
private
listItems:TlistBox;
function GetItems(Index:Integer):String;
procedure SetItems(Index:Integer:const Value:String);
public
property Items[Index:Integer]:string;
end;
function TFormDialog.GetItems(Index:Integer):string;
begin
if Index >=ListItems.Items.Count then
raise Exception.Create(‘TformDialog:Out of Range’);
Result:=ListItems.Items[Index];
end;
procedure TformDialog.SetItems(Index:Integer;const alue:string);
begin
if Index >=ListItems.Items.Count then
raise Exception.Create(‘TformDialog:Out of Range’);
ListItems.Items[Index]:=Value;
end;
规则13:使用属性的附加作用(Use Side-Effects In Properties)
请记住:使用属性而不是访问全局变量(参见规则10、11、12)的好处之一就是当你设置或者读取属性的值时,你还可能有意想不到的收获。
例如,你可以直接在窗体界面上拖拉组件,设置多个属性的值,调用特殊方法,立即改变多个组件的状态,或者撤销一个事件(如果需要的话)等等。
规则14:隐藏组件(Hide Components)
我经常听见那些面向对象编程的狂热追求者抱怨Delphi窗体中包含一些在published部分声明的组件,这是和面向对象思想的封装性原理不相符合的。他们确实提出了一个重要的议题,但是他们中的大多数人都没有意识到解决方法其实就在他们手边,完全不用重写Delphi代码,也不用转向其他语言。
Delphi向窗体中添加的组件参照可以被移到private部分,使得其他窗体不能访问他们。如果你这样做,你就有必要设置一些指向组件的窗体属性(请参见规则11),并且使用它们来访问组件的状态。
Delphi将所有的这些组件都放在published部分,这是因为使用这种方式能够保证这些域一定是在.DFM文件中创建的组件。当你改变一个组件的名称时,VCL能够自动地将这个组件对象与它在窗体中的参照关联起来。因为delphi使用RTTI和Tobject方法来实现这种功能,所以如果想要使用这种自动实现功能,就必须把参照放置在published部分(这也正是为什么delphi将所有的组件都放在published部分的缘故)。
如果你想知道的更详细一点,可以参看下面的代码:
procedure Tcomponent.SetReference(Enable:Boolean);
var
Field:^Tcomponent;
begin
If Fowner<> nil then begin
Field:=Fowner.FieldAddress(Fname);
If Field<>nil then
Field^:=Self
else
Field^:=nil;
end;
end;
上面的代码是Tcomponent类的SetReference方法,这个方法可以被InserComponent,RemoveComponent和SetName等方法调用。
当你理解了这一点后,你应该不难想到如果你将组件参照从published部分移到了private段,你将失去VCL的自动关联功能。为了解决这个问题,你可以通过在窗体的OnCreate事件中添加如下代码解决:
Edit1:=FindComponent(‘Edit1’) as Tedit;
你接下来应该做的就是在系统中注册这些组件类,当你为他们注册过后就能使RTTI包含在编译程序中并且能够被系统所使用。当你将这些类型的组件参照移到private部分时,对于每一个组件类,你只需为他们注册一次。即使为他们注册不是一定必要的时候,你也可以这样做,因为对于RegisterClasses的额外调用有益无害。通常你应该在单元中负责生成窗体的初始化部分添加以下的代码:
RegisterClass([TEdit]);
规则15:面向对象编程的窗体向导(The OOP Form Wizard)
为每一个窗体的每一个组件重复上述两个操作不仅十分的烦人,而且相当的浪费时间。为了避免额外的负担,我已经为此写了一个简单的向导程序。这个程序将会生成一些可以完成以上两步工作的代码,你需要做的仅仅是做几次复制和粘贴就行了。
遗憾的是这个向导程序不能自动将代码放置到单元中合适的地方,我目前正在修改这个向导程序,希望能实现这个功能。你可以到我的网站(www.marcocantu.com)查找更加完善的程序。
规则16:可视化窗体继承(Visual Form Inheritance)
如果应用得当,这将是一个强大的工具。根据我的经验,你所开发的项目越大,越能体现它的价值。在一个复杂的程序中,你可以使用窗体的不同等级关系来处理一组相关窗体的多态性(polymorphism)。
可视化窗体继承允许你共享多个窗体的一些公共的动作:你可以使用共享的方法,公用的属性,甚至是事件处理程序,组件,组件属性,组件事件处理方法等等。
规则17:限制保护域数据的使用(Limit Protected Data)
当创建一些具有不同分级体系的类时,一些程序员趋向于主要使用保护域,因为私有数据不能被子类访问。我不能说这没有其合理性,但是这肯定是和封装性不相容和的。保护数据的实现能够被所有继承的窗体所共享,而且一旦这些数据的原始定义发生改变,你必须更改所有的相关部分。
请注意,如果你遵循隐藏组件这样一条规则(Rule 14),继承窗体就不可能访问基类的私有组件。在一个继承窗体中,类似Edit1.Text:=’’的代码就不会被编译。虽然这是相当的不方便,但是至少在理论上这是值得肯定的事情,而不是否定的。如果你感觉到实现封装性是最主要,最需要的,就请将这些组件参照放在基类的私有段。
规则18:保护域中的访问方法(Protected Access Methods)
在基类中将组件参照放置在私有域中,而为这些组件添加一些访问函数来得到他们的属性,这将是一种更好的方法。如果这些访问函数仅仅在这些类内部使用而且不是类接口的一部分,你应该在保护域声明他们。例如Rule 11中描述过的GetText和SetText方法就可以声明成protected,并且我们可以通过调用SetText(’’)来编辑文本。
事实上,当一个方法被镜像到一个属性时,我们可以简单地采用如下代码就可以达到编辑文本地目的:
Text:=’’;
规则19:保护域中的虚拟方法(Protected Virtual Methods)
实现一个灵活的分级制度的另一个关键点是定义一些你可以从外部类调用的虚拟方法来得到多态性。如果这个方法使用得当,将会很少出现其他公共的方法调用保护域中的虚拟方法的情况。这是一个重要的技巧,因为你可以定制派生类的虚拟方法,来修改对象的动作。
规则20:用于属性的虚拟方法(Virtual Methods For Properties)
即使是访问属性的方法也能定义成virtual,这样派生类就能改变属性的动作而不必重定义他们。虽然这种方法在VCL当中很少使用,但是它确实十分灵活、强大。为了实现这一点,仅仅需要将Rule 11当中的Get 和Set 方法定义成Virtual。基类的代码如下所示:
type
TformDialog = class ( TForm)
Procedure FormCreate(Sender:Tobject);
Private
Edit1:Tedit;
Protected
function GetText:String;virtual;
procedure SetText(const Value:String);virtual;
public
constructor Create(Text :String):reintroduce;overload;
property Text:String read GetText write SetText;
end;
在继承窗体中,你可以添加一些额外的动作来重载虚拟方法SetText:
procedure TformInherit.SetText(const Value:String);
begin
inherited SetText(Value);
if Value=’’ then
Button1.Enabled:=False;
end;
小结
要做到一个好的Delphi面向对象编程程序员远非我在上面提到的这些规则这么简单。上面的这20条规则中有一些可能需要足够的耐性和时间来实现,因此,我也不能强求你能遵循所有的这些规则。但是这些规则应该被合适的运用到你的程序中,而且当你开发的应用程序越大,参与的程序员越多,这些规则越重要。不过,即使是一些小程序,始终记住这些规则并在合适的地方使用他们也会对你有所帮助。
当然,还有很多其他的经验规则我没有涉及到,特别是存储器处理和RTTI问题,因为他们十分的复杂,需要专门的说明。
我的结论是要遵循我上面列出的规则会付出一定的代价,特别是额外的代码,但是这些代价会让你得到一个更加灵活强壮的程序。希望Delphi的后续版本能够帮组我们减少这些代价。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
******************************************************************
* 四、写代码的一些要素 *
******************************************************************
A. 尽可能不要在(对新手来说,是绝不要)
TMyForm=class(TForm)
.....
.....
.....
end;
之外定义变量,尽量在Private段定义自己的变量和Procedure等。
B. 任何情况下,不论多么艰难的调试情况,在begin回车之后,先打两个空
格再写程序代码。end一定要和相应的begin呼应在同一列上。
C. Delphi是大小写不敏感的,但是多处引用时尽量保持相同的拼写。
D. 不要自己去删Delphi自动生成的代码,解铃还需系铃人,如果不小心在哪
里Double Click了一下,Delphi生成的程序会在Save Project和Build时
自动删掉。(只要begin...end中间没写你自己的代码)
E. FormCreat之前,它所包含的控件都还没有建立,是不能引用的。在Form1OnCreat
时,你不能用
Form1.ListBox1.......
但是你可以写:
ListBox1.........
这是Delphi的约定,所以不要问我为什么。
F. Form1如果是MainForm, 在OnCreat时是不能去引用Form2的变量的,因为这
时Form2还不存在。
G. Form2.ShowModal后,程序会在Form2返回后才执行后面的代码,而用Show
可不是这样。
H. 任何一段消息回调函数的代码都不要执行过长时间,如果迫不得已,在大循
环的间隙,一定要调用
Application.ProcessMessages
要不,程序会象死机一样。
******************************************************************
* 五、Windows编程的重入问题 *
******************************************************************
TTimer是用于定时反复执行一些代码,可是千万记住如果一段代码执行时间
过长时,下一次Timer触发会重入这段代码,就是说如果Timer1Timer中没有
Timer1.Enabled := False;
的话,可能会有两个进程在执行这段代码,如果共同访问一个变量,一个有
可能在改,一个有可能在读,那么就会出现意想不到的问题。
类似的重入问题其它时候也会碰到,比如你在Show(而不是ShowModal)一个
Form时,两个Visible的Form在共同访问同一变量,就会出现一些奇怪的现象。
新手最好先避开这类问题,省得麻烦。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
作者: Musicwind?
有关开发习惯的一些想法,如鲠在喉,不吐不快。究其发贴动机,当然不排除有骗取参与分的可能,但另一方面,也希望能给同行(念Xing)者提供一些建议,或者参考(希望不是误人子弟)。同时,也希望各位能够就我的这些陋习,发表一点看法,给出批评和指正的意见。谢谢。
一.建立工程目录
首先,第一步要做的,当然是给新项目建一个单独的目录(别笑)。目录名称与项目名称同名,或者另取一个也可,只要清楚、简练。然后,在此目录中创建以下各个目录:
:用来存放该项目相关的开发文档(需求说明,概要设计,详细设计等等等等);
:该目录中存放”.Dcu”文件,将’.Pas’与’.Dcu’文件分开存放只是为了让Source目录的内容更加清楚一些;
:存放工程的输出文件,比如”.Exe”,”.Dll”或者”.Ocx”等等;
:用来存放日志文件;通常在这个目录中我会放一个”<项目名称>程序员日志.Txt”文件。
:当然是存放工程中用到的图片的目录了。一般情况下,这个目录是少不了的。假如还用到其他资源,那么也一样建立各自的目录,比如Wav,比如Avi等等。
二.设置工程选项
在Delphi中创建一个新的工程,将此工程保存到Source目录中,同时:
a. 选一个耐看的,与项目有些联系的图标作为这个工程的图标。当然,这个图标可能只是临时用用的,但是总要比Delphi默认的那个难看的要好才行,要不然,怎么对得起自己?
b. 将Project Options -> Directories/Conditionals页面中的Output Directory设置为Bin目录;
c. 将Unit output Directory设置为Dcu目录。
三.添加常量单元
添加一个新的Unit,另存为“unt<工程名> Consts.Pas”,用来保存工程中用到的常量。
四.有关窗体(Form)及单元(Unit)
按照匈牙利命名法则给Form命名,则一个用来登录的窗体可以命名为’FrmLogin’,而其单元名可以为’untLogin’。通常,两个对应的Form和Unit的名称在除去’Frm’或’unt’的缩写后应当保持一致。
在Unit的头部添加本单元的注释,注释的格式可以参照Delphi的源码,但是至少应当包含以下几项:功能描述;作者;版权;创建时间;最后修改时间;修改历史等等。
将新创建好的Form的Caption设置为该Form类的名称,而不是使用Delphi默认的。比如,将Form1更名为FrmLogin后,此时我们获得了TFrmLogin这个新的窗体类,并且Delphi自动将窗体的Caption更新为’FrmLogin’。依我看,该Caption应当为’TFrmLogin’才是,因为我们在设计的是一个窗体类TFrmLogin,而不是仅仅对FrmLogin进行操作。
向TFrmLogin这样功能明确的窗体类,许多人都有在设计期就将其Caption设置为诸如“操作员登录”这种名称的习惯。我的习惯是,象“操作员登录”这样的常量,通常存放在unt<工程名>Consts.Pas中,用ResourceString来定义,或者用Const来定义。至于窗体的Caption的命名,应当属于运行期的工作。所以,我往往在TForm.OnCreate事件触发之时才对Caption进行操作,比如:
procedure TFrmLogin.FormCreate(Sender: TObject);
begin
Caption := csLoginTitle;
....
end;
五.关于Format函数的使用
有iYear,iMonth,iDay三个数据,要显示诸如“生日:1976/3/18”这样的信息,你通常怎么做?使用s := ‘生日:’+IntToStr(iYear)+’.’+IntToStr(iMonth)+’.’+IntToStr(iDay); 吗?这样实在是太累了。我的习惯是,在unt<工程名>Consts.Pas中增加一个常量csBirthDayFormat = ‘生日:%d/%d/%d’来保存显示格式,然后使用s := Format(csBirthDayFormat, [iYear, iMonth, iDay]);这样的语句完成数据的拼装。这么做的好处显而易见,那就是你只需在一个地方维护数据的显示格式。
Format函数功能强大,我对它很是推崇,你呢?
六.关于注册表或者Ini文件的存储
原先访问注册表我通常使用TRegistry,而访问Ini文件通常使用TIniFile。这两个类的使用方法各不相同,因此想要使用相同的代码既能访问注册表又能访问Ini文件几乎是不可能的。真头疼啊!
终于我发现了救星!那就是TRegistryIniFile类。查看Registry单元,我们发现,TRegistryIniFile继承自TCusomIniFile。而TIniFile也是继承于TCusomIniFile。因此,使用抽象类TCusomIniFile来实现对注册表或者Ini文件的访问便是一举两得了。比如:
var
csmIniFile: TCusomIniFile;
begin
if blUseIniFile then//如果使用Ini文件
csmIniFile:= TIniFile.Create(csRootKey)
else
csmIniFile:= TRegistryIniFile.Create(csRootKey);
//接着就可以使用csmIniFile对Ini文件进行访问,
//或者用类似访问Ini文件的方式访问注册表。
七.关于TStream流以及TFileStream,TMemoryStream等等
TFileStream和TMemoryStream都继承自抽象类TStream,这意味着我们可以使用一套代码完成对文件和内存的存取操作。因此,定义一些接口的时候,我往往倾向于将参数的类型定义为抽象类,而不是具体类。比如,要完成保存功能的一个函数,定义成
function Save(AStream: TStream): Boolean;
就比定义成
function Save(AStream: TFileStream): Boolean;
要灵活的多。
前一个定义是具有前瞻性的,因为它可以适用于以后可能出现的新型态的流。而后一个定义只适用于TFileStream这种流(当然包括TFileStream的子类),呆板多了。
我的习惯:如果存在抽象类,那么尽量将参数定义为抽象类的类型,毕竟,我们无法预见未来。
八.多使用TAction
Delphi 4以后引入了Action的概念,并且在Standard组件栏中增加TActionList组件。使用Action的好处是,控件状态同步的烦恼从此一扫而空!
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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的各个角落里都蕴涵真正的赤金,请发现者能够将自己的心得发现公布与众,给大家提供一条通往知识宝库的钥匙。