第6章接口的奥秘_第1页
第6章接口的奥秘_第2页
第6章接口的奥秘_第3页
第6章接口的奥秘_第4页
第6章接口的奥秘_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第6章接口的奥秘消息处理程序、过程类型以及事件处理程序把Delphi程序与Windows操作系统联系在一起。这就是说程序是嵌入到Windows中的。Windows不能也无法预知运行在其中的程序,因为当Windows出现时,大部分应用软件还没有编写出来。当Windows出现时,它必须提供一种途径,使得应用程序可以响应操作系统。最后的结果是:Windows成为了一个基于消息的操作系统,而Windows程序必须响应这些消息。我将其称之为邮政服务式体系结构。最重要的是:通过响应消息,Windows应用程序只需与Windows系统松散耦合。所有的Windows程序都必须响应消息,而Delphi程序对此尤

2、为出色。Windows程序必须可以与任何程序通讯,而无须预先知道该特定程序所响应的消息和响应的方式。仿照Delphi中使用过程类型、事件特性和事件处理程序的方式,就可以隐藏Windows笨重的消息和事件驱动体系结构,并屏蔽不同的Windows消息和消息记录。实际上,也就是用通常的Pascal过程来屏蔽Windows消息处理程序和消息记录。本章讨论了消息处理程序、过程类型和事件处理程序,它们在用Delphi编写的Windows程序中随处可见。由于这些技术有助于使您的程序成为整洁、健壮、出色工作的独立子系统,本章中完整地涵盖了有关的内容。6.1 赢得对意大利细面条的战争所谓的意大利细面条式代码,指

3、的是耦合代码。在避免耦合代码这一点上,基本上每个人都是口惠而实不至,因此代码很容易出现耦合。赢得战争的关键策略是,采用所有可能避免耦合代码的技术,并使这些技术成为根深蒂固的习惯。要做到这一点,您有必要了解一些术语,它们有助于维护模块的分离、独立,从而不至于成为相互依赖的大块耦合代码。这里有一个代码有害的例子,您可能以前见到过。unit Unit1;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls,Forms, Dialogs,StdCtrls;typeTForm1 = class(TForm)Button

4、1: TButton;procedure Button1Click(Sender: TObject);private Private declarations public Public declarations Canceled : Boolean;Procedure Process;end;varForm1: TForm1;implementationuses Unit2;$R *.DFMprocedure TForm1.Button1Click(Sender: TObject);beginProcess;end;procedure TForm1.Process;varI : Intege

5、r;beginCanceled := False;Form2 := TForm2.Create(Self);tryForm2.Show;for I := 1 to 10 dobeginif( Canceled ) then break;Sleep( 1000 ); / simulates some processingForm2.ProgressBar1.Position := Trunc(I *Form2.ProgressBar1.Max / 10);Application.ProcessMessages;end;finallyForm2.Free;end;end;end.unit Unit

6、2;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls,Forms, Dialogs,StdCtrls, ComCtrls;typeTForm2 = class(TForm)Button1: TButton;ProgressBar1: TProgressBar;procedure Button1Click(Sender: TObject);private Private declarations public Public declarations end;varForm2: TForm2;implemen

7、tationuses Unit1;$R *.DFMprocedure TForm2.Button1Click(Sender: TObject);beginForm1.Canceled := True;end;end.在列出的代码中,Form1模拟了主窗体,其中包含了过程Process的代码。该类定义了名为Canceled的公有布尔类型成员。在Form1中对Form2进行了实例化。对Sleep的调用模拟了Form1中可能进行的处理过程。Form1直接修改了Form2中的进度条(见图6.1)。处理将一直进行,直至所有的项都处理完毕或者Canceled被设置为True。在Form2的Button1C

8、lick事件处理程序中修改了Canceled特性。结果是,Form1必须很清楚地知道Form2的存在,反之亦然,二者之间的联系非常紧密(很清楚,二者是互相“了解”的,因为两个单元的实现部分的uses子句进行了相互引用)。图6.1负责进度条更新的窗体当程序的规模十分有限时,这种类型的代码很容易被忽视。不幸的是,有用的软件很少是简单的。如果两个窗体中有一个变为对话框组件,问题就更糟糕了。考虑Form2变成对话框组件的情况(对话框组件的更多信息请参见第10章)。进一步可以认为Form1是某个复杂应用程序的主窗体,因此它拥有该应用程序中大多数的其他窗体。把Form2添加到VCL中(它是个组件,我们可以

9、这样做),您的整个应用程序就都添加到了VCL中(不要笑,确实如此)。最后的结果形成了一个臃肿的VCL库,它依赖于非VCL的代码。当您编译应用程序时,VCL代码也会重新进行编译。当您建立VCL库时,您的应用程序也将被编译。如果出了错,组件就无法建立因而也无法装载。真是一团糟。如果还要试着给其他开发者讲述这乱成一团的代码、其工作原理、以及如何对其进行修改等,那简直是代价高昂而且灭绝人性的行为。注意:公平地讲,必须提到定义接口会引入另一种复杂性。这与一次性付款和延期付款孰优孰劣的问题颇为相似。通过定义接口,可以使您更快地写出更多的代码。而当代码模块之间相互关系的数目和复杂程度已经难于控制时,试图解决

10、问题可能为时已晚,因此,通过精确的接口提高代码的质量是更为可取的做法。即使对于非常简单的应用程序,定义接口的方法也可以得到非常好的效果。不幸的是,这种方法需要进行训练。程序员和审阅者需要注意并经常修改代码之间的关系,并对其提出一些简化方案。从长远看来,将解决问题的时机拖后代价要更高,等问题拖到了不能不解决的时候,可能就太晚了。窗体或数据模块之外的紧耦合代码,同样会导致问题。两个并非窗体的类也会产生前面的代码中的问题,如果在项目中很晚才发现这种行为,将使产品的完成期限和交付产生严重的问题。如果Form1使用并显示Form2,那么很清楚,Form1与Form2之间的关系一种是拥有性质:Form1拥

11、有Form2。当打破类的边界时,程序的复杂性就会增加。上面的例子中,出现了两处这样的实例:Form1引用了Form2.ProgressBar1.Position,而Form2引用了Form1.Canceled.Form2.ProgressBar1.Position,这就是代码质量很差的原因所在,因为它打破了两个对象Form1和ProgressBar1的边界。如果改变状态的实现方式,则Form1和Form2都需要进行改变。改动是代价昂贵的。本章的其余部分将提出一些策略,在更高的层次上向您提供与Delphi的高级术语密切相关的知识,这将有助于您写出更为独立、修改更少的代码,并且减少了代码演化时的麻

12、烦。6.2 类定义实用指南在学校里,主要的课程可能是有关语法与数据结构的。很少有大学和学院会讲授一个好的程序的组成要素,因为其答案过于主观。如果您确实对此有所了解,那可能是投入大量时间的和几经碰壁之后得到的。事实上,无须碰壁就可以学到一些好习惯。程序设计已经出现很多年了,许多程序员也编了很多年的程序,有一些好的惯例已经牢固的确立。遵循这些惯例,即可编出一些出色的程序。Delphi就是这些程序之一。Delphi的源代码是软件业的迈克尔乔丹。当然,向Delphi的源代码学习需要阅读很多代码。Delphi的源代码并非有关知识的惟一来源。可以找到大量相关的例子和指南,但并不存在惟一的知识来源,即所谓“

13、最好的惯例手册”。一些思想仍然被认为令人讨厌、过于主观。但确实有一些惯例被无争议的采用,它们将有助于您编写出更好的代码。6.2.1 类中有什么有大约半打的最好的惯例,可有助于定义好的类。1由Booch的书(Booch,第137页)可知,类应该是简单的。这意味着基本上没有公有权限的属性。在Delphi的类中,大约会有半打左右的方法和特性。2要使公有权限的接口尽早稳定,并使其数量尽可能少,这样程序员可以从类的简单行为建立较为高级的行为。3某个类都有一个可识别的主要函数。4将数据封装在私有权限的接口中,通过公有特性和支持特性的方法进行访问。5通过子类化来扩展类的行为,减少对已存在代码的影响,最小化重

14、新测试的可能性。6要明白,第一次就得到最好的抽象模型可能很困难。当理解了有关问题域的更多信息后,要准备好对抽象模型进行修改。一个程序员曾表示,在单一的应用程序中出现几个类表明对程序设计缺乏基本的理解。很显然,这是错误的观点。从拉丁名言“分而治之”可知,反过来才是对的。通常,错误的抽象模型或缺乏抽象模型证明对面向对象程序设计缺乏基础知识。注意:断言代码质量的陈述是基于所谓的专家意见。诸如“这就是我们在XYZ公司做事的方法,因此它是最好的”之类的陈述。这是孤立工作的程序员的通病。关于什么是最好的惯例有许多断言,但其中大部分都指出对大量经验证据进行仔细思考后才能作出精确而科学的发现。例如在Booch

15、的书中提到,对质量的定性度量是基于耦合、内聚、充足性、完全性以及原子性等(Booch,1994)。而缺乏经验或支持信息的纯粹主观论断,是非常值得怀疑的。应用“分而治之”的告诫,我们应把复杂的问题分解为一系列简单而基本的问题,并分别解决某个问题。本节开头的指南较为通用,可成为很好的起点。如果在类的公有接口部分定义了很多属性,代码会变得更复杂,反过来会影响您或其他程序员对代码的控制能力。6.2.2 没有数据的类许多规则都有例外。一般的,如果类定义中没有数据,按照通常的规则应把该类合并到其他类中,因为这种没有数据的类形式并未捕捉到问题域的完全的抽象。有一种类是该规则的例外,可称之为工具类。如果类的属

16、性都是方法,则通常将其定义为类方法,这样无论有无对象实例均可使用方法。提示:按照通常的规则,类应有数据和方法。数据记录状态而方法定义行为。只有在确实需要时,才可以背离该规则。在Delphi中,TObject类是所有类的基类。它包含了几个类方法,通常只在其子类实例化时才间接地创建该类的实例。对于所有类都应有数据的通常规则,TObject就是个例外,定义该类是为了使所有的Delphi类都具有某些基本能力,有助于它们在Delphi程序中发挥作用。6.2.3 命名惯例Delphi并未强加任何令人难以忍受的命名惯例。Delphi开发者所使用的一些惯例是基于规则的,而不是基于需要记忆的前缀,您可以自由选择

17、是否遵从该惯例。但如果您试过,可能会认为它们是易于使用的,而且简化了编程。方法的命名惯例方法是动词与名词的组合。动词描述了动作,并且在名词之前,而名词则描述动作所施行的对象。我们还知道名词与动词合起来,足以明确表达一个完整的口语或书面的句子。因此名词与动词联合的名字具有很高的可读性。按照规则,要把方法的作用域限制到方法名中的动作和主题范围之内。如果在方法名中只有一个名词,那么您可能是在处理特性。按照惯例,特性方法中读方法的前缀为动词Get,而写方法的前缀为动词Set,其后紧接着特性名(高级特性编程的更多信息请参见第8章)。事件处理程序的命名惯例Delphi使用介词On作为事件处理程序的前缀。O

18、n描述了动作或运动,如OnClick或OnDragDrop。通过遵循一些惯例,几乎不需要花费时间即可找到方法、事件或特性的名字。术语的类型、动作和动作的主题可以帮助您为代码命名。数据的命名惯例Delphi中的数据属性称之为字段。按照惯例,私有字段的前缀为F。去掉F,即可得到表示实际字段的特性的很方便的名字。请记住过程类型,即事件和数据也可以是字段,因此前缀为F。将字段与特性匹配很简单,将字段名去掉F前缀即可。前面提到过,基于规则的惯例可以使得代码在外观上一致而可靠,并可以减轻命名时想方设法的烦恼。遵守Delphi的命名惯例与否,是您个人的选择。推荐您使用一种可辨别的风格,并一直坚持使用。消息处

19、理程序的命名惯例消息处理程序是一些特别的方法,用于响应Delphi所实现的消息分发模型。按照规则,消息处理程序与其所响应的消息名字大致相同。许多Windows消息的前缀为WM_,而Delphi对消息方法名的前两个字符使用了WM。与特定的控件相关的消息的前缀也具有特别的前缀,例如前缀为CB_的组合框。在messages.pas中可以找到这些消息的名字,它们被定义为常数。6.2.4 存取限定符的使用Delphi帮助文档中称存取限定符定义了成员的可见性。这有点用词不当,因为存取限定符并非限制代码的可见性,而是限制代码的可访问性。存取限定符将代码划分为四种不同的可访问层次和大体上三个可访问区域。公有和

20、公开区域指明了类的用户可以访问而且应该关心的那部分代码。保护权限指明了扩展一个类时,除了公有权限的代码之外,还需要注意的代码,最后,私有权限表示只有作者自己才会看到的代码。仔细而适中地将代码分布在不同的访问区域中,对于预期的用户可提高类所发挥的效用。对于公有访问权限来说,越少越好。要维持对公有属性的紧密控制,以确保您的类可以通过代码质量的原子性度量。6.2.5 默认的公开或公有权限默认情况下,所有的属性都具有公有权限,这与C+并不相同,在C+中属性在默认情况下是私有的。如果类编译时添加了运行时类型信息(在$M+状态下编译),如TPersistent类及其所有的子类,则所有属性默认情况下具有公开

21、权限。当在工程中创建一个新的窗体时,所有的组件都出现在窗体定义的上部。警告:最好由Delphi来管理位于窗体类上部和.DFM文件中的那些属性。如果您希望自己来做,也可以手工进行管理,但需要非常小心。Delphi的行为是一致的,它并未把在窗体上绘制的控件与其他类区分开来,即使对于可以从.DFM文件中读写窗体定义并使用脚本消息来自动实例化组件的流类也是如此。考虑本章开头例子中的进度条窗体。该窗体中有一个TProgressBar和一个TButton对象。Delphi对这些组件流化了足够的消息,以便在创建Form2的实例时自动创建这些组件。object Form2: TForm2Left = 441T

22、op = 222Width = 263Height = 163Caption = 'Progress'Color = clBtnFaceFont.Charset = DEFAULT_CHARSETFont.Color = clWindowTextFont.Height = -13Font.Name = 'MS Sans Serif'Font.Style = OldCreateOrder = FalsePixelsPerInch = 120TextHeight = 16object Button1: TButtonLeft = 88Top = 88Width =

23、75Height = 25Caption = 'Cancel'TabOrder = 0OnClick = Button1Clickendobject ProgressBar1: TProgressBarLeft = 24Top = 24Width = 217Height = 25Min = 0Max = 100TabOrder = 1endend当读到object语句时(上面列出的.DFM文件中有三个),Delphi将从object语句一行判断对象的类,创建该对象的实例,并读入其余的属性,这就是DefineProperties方法的任务。由于组件有构造函数和析构函数,因此您可以

24、选择在设计时将其添加到应用程序中或在运行时动态地创建它们。在任何TWinControl控件上,都可以动态地创建并初始化控件。TWinControl控件可以拥有控件。要在窗体上动态地创建TButton控件,可使用下面的代码,其中Self参数代表该窗体。with TButton.Create(Self) dobeginParent := Self;Name := 'ButtonProcess'OnClick := Button1Click;SetBounds( 10, 10, Width, Height );Caption := 'Process'end;注意:TW

25、inControl类维护了一个TControl的列表,由TWinControl所拥有的子控件组成。因此,虽然并未显式保存对控件列表中动态创建对象的引用,通过搜索控件列表来查找名为ButtonProcess的TButton控件即可得到该引用。上面的代码模拟了Delphi在读入资源文件并创建窗体时的行为。上面列出的代码与设计时添加按钮的区别在于,上面的代码添加了一个按钮到窗体,但并未在DFM文件中维护对该按钮的引用,而设计时也无法操纵动态生成的按钮。6.2.6 公开接口当使用published存取限定符时,表示该属性将出现在Object Inspector中。除此之外它与公有访问权限是相同的。把方

26、法放在公开部分是没有意义的,这与将其放在公有部分效果相同。当定义既可在设计时又可在运行时修改的组件特性时,可将其访问权限定义为公开权限。如果您不需要在设计时修改特性,则无须将其定义为公开权限。事件特性也可以是公开的,组件公开的事件特性会在Object Inspector的Events属性页中列出。Delphi 6中新增了公开对象特性的概念。在Delphi的早期版本中,如果创建包含其他组件的组件,只能使用属性提升的手段来访问内部对象的特性。例如,如果要在设计时修改对话框控件上的图像,则只能向该组件添加一个图像特性,然后利用图像特性的方法来访问实际的TImage控件的Picture特性。现在您可以

27、把TImage控件作为公开特性,并直接访问其picture特性。这是对公开访问区域的一个很好的改进(对象特性的更多知识及相应实例,请参见第10章)。6.2.7 公有接口公有接口是类怎样使用的决定性因素。如果无法利用公有接口达到目的,那么也就没有使用该类的必要。按照Booch对代码质量的测量标准,公有接口中的属性应该足够、完全、并具有原子性。足够指的是该类足以解决问题。即公有接口应是自给自足的。例如,一个文件流类中有写功能,要达到足够并完全,需要在该类中添加兼容的读功能。原子性指的是类的功能应是基本的,如果一个类方法建立在另一个公有方法基础之上,则第二个方法不是原子性的,应从类中去掉。应使公有接

28、口保持简明,并尽早地定义类的公有接口并使之稳定。如果在一个类的公有接口的基础上建立了其他类,则修改该类也会导致对依赖于它的类的修改。类中非公有的属性都应该是保护或私有的。6.2.8 保护接口在面向对象中,保护接口供扩展该类的程序员使用。扩展意味着从已有的类派生子类,扩展其行为和状态。把属性放在保护接口中时,对类进行扩展的程序员就可以修改这些属性。像古老的格言所说“可以做的事都会有人做”,如果您不希望在子类中改变方法或直接操纵属性,那就使用私有接口吧。6.2.9 私有接口私有接口与公有接口是并列的。从外部看来,只有构成类的基本行为和状态的属性才可以放在公有接口中,而私有接口好比是清洁工具柜。除非

29、有意地使扩展该类的程序员可以访问它们,否则一切用于实现类的公有行为的属性都会放在私有接口中。注意:在私有与保护权限之间进行选择是困难的,因为这几乎与预先判断其他开发者然后使用您的代码一样,都是不可能的。如果您的用户是一些特定的开发者,例如组件的作者,可以考虑违反规则,通过将更多的数据和方法设置为保护权限,从而使代码的扩展性更好。这也会使扩展您的组件的开发者有更多的灵活性。请把类的所有实现细节放在私有访问区域中。这样做可以清楚地向用户表明,他们无须关心这些项,因此可以使得您的类更易于使用。在定义类时,可以最后解决私有实现细节,因为在类改动时,它们对用户的影响最小。6.3 创建自定义过程类型Del

30、phi和C+支持函数指针。而Visual Basic和Java则不支持。函数指针是个强有力的概念,它在过程一级提供了一个额外的动态层次。我们提到过,可以把回调过程传递给Windows,这样就能让操作系统来调用回调过程。C+中的函数指针例子如下:#include "stdafx.h"#include <iostream.h>void (*fp)();void Function()cout << "Hello World!" << endl;int main(int argc, char* argv)fp = Functi

31、on;fp();return 0;void (*fp)();一行定义了一个名为fp的函数指针变量。通过把过程Function的名字赋值给fp,就可以通过fp调用Function。函数指针是很高级的概念,它向程序员提供了额外的实现选项(例子请参见节“回调过程”)。Delphi对函数指针的支持不那么难懂。函数指针的概念在Delphi中称之为过程类型。过程类型与其他类型的声明在外观上是一致的,无须像C+中那样进行间接引用。使用过程类型,可以编写支持Windows回调过程、动态过程参数和事件处理程序的代码。它们的共同特征就是可以编写出动态和富于表达力的代码,而且难于在任何其他的语言中复制。6.3.1

32、定义过程类型当定义过程类型时,可以将该类型作为别名引入,也可以将其作为新的类型引入,对于后者,编译器将进行强类型检查;该类型可以指定为方法或非方法的过程类型。过程类型简单的过程类型定义在类型声明部分。与前面的C+代码一致的过程类型可如下声明。type TProcedure = Procedure;上面的声明意味着,任何没有参数的过程都可以赋值给TProcedure类型的变量。如果要声明有参数的过程类型,可以像声明过程那样包括参数列表,只需去掉过程名既可。typeTIntegerProcedure = Procedure( I : Integer);TObjectProcedure = Proc

33、edure( Sender : TObject );TManyParams = Procedure( S : String; I : Integer );过程类型定义的参数列表可以与过程声明一样多变。可以包括数组参数、变量列表、常数和输出参数。决定性因素是您的需求。所需引用的过程的类型将决定您所需要的过程类型。函数类型在对应于过程和函数的过程类型之间,不存在实际的区别。定义语句也是相同的,除了用关键字Function代替Procedure外,在定义语句的末尾添加返回类型即可。typeTFunction = Function : Integer;TStringFunction = Functio

34、n( S: String ) : Boolean;TVarFunction = Function( var D : Double ): String;如同过程类型一样,函数类型的参数列表是由该类型所引用的函数的参数所决定的。返回类型也必须匹配。用于方法的过程类型过程类型也能引用方法,可以是类的成员函数或成员过程。当定义方法指针,即用于方法的过程类型时,需要表明该过程类型指向类的成员。在类型定义的结尾用of Object标记即可。typeTFunctionMethod = Function : Integer of Object;TProcedureMethod = Procedure( var

35、 S : String ) of Object;除了定义结尾的of Object限定符以外,该类型定义与非方法的类型定义是相同的。在Delphi中遇到的过程类型通常是事件特性的类型。最常用的方法指针类型是在classes.pas中定义的TNotifyEvent。type TNotifyEvent = procedure (Sender: TObject) of object;该类型用于许多事件特性,包括您会经常用到的OnClick事件。6.3.2 回调过程回调过程是这样一种过程,其地址被赋值给变量或作为参数传递,用于在指定的时间进行调用。Windows和Delphi都支持回调。回调过程的最常见

36、的用途是在对象和响应事件的动作之间提供一个松散的耦合点。例如,鼠标单击行为就是这样引入到TControl控件中的。procedure WMLButtonUp(var Message: TWMLButtonUp); messageWM_LBUTTONUP;procedure TControl.WMLButtonUp(var Message: TWMLButtonUp);begininherited;if csCaptureMouse in ControlStyle then MouseCapture := False;if csClicked in ControlState thenbeginE

37、xclude(FControlState, csClicked);if PtInRect(ClientRect, SmallPointToPoint(Message.Pos) thenClick;end;DoMouseUp(Message, mbLeft);end;procedure TControl.Click;begin Call OnClick if assigned and not equal to associated action'sOnExecute. If associated action's OnExecute assigned thencall it, o

38、therwise, call OnClick. if Assigned(FOnClick) and (Action <> nil)and (FOnClick <> Action.OnExecute)then FOnClick(Self)else if not (csDesigning in ComponentState)and (ActionLink <> nil) thenActionLink.Executeelse if Assigned(FOnClick) thenFOnClick(Self);end;TControl类建立了自己的WndProc过程,

39、作为对Windows消息的回调过程。当WndProc从Windows收到WM_LBUTTONUP消息时,它通过在TObject中引入的Dispatch方法将消息分发到控件。Dispatch调用上面列出的WMLButtonUp消息方法。该消息方法确保了控件可以像设计的那样接收鼠标单击。if( csClicked in ControlState ) then如果控件接收到单击,则调用动态过程Click。动态方法表会调用正确的Click方法。处理单击的方法还会检查FOnClick事件处理程序是否指向有效的过程。if( Assigned(FOnClick) then FOnClick(Self);如果

40、Assigned(FOnClick)结果为True,就会调用该过程。一个通情达理的人可能问的第一个问题会是,为什么需要绕这么多圈子才能知道发生了鼠标单击?只有从某一角度看来,答案才是显然的。您确实不必这样做,因为所有的这些信息在日常的开发活动中是不可见的。Windows程序设计在十年前被认为很困难。而现在所有的Delphi程序员都只需双击Object Inspector中的OnClick事件特性,然后填写方法体中的空白即可。Windows消息机制的复杂性在封装后对于日常的程序员是不可见的,复杂性的隐藏使事情变得很容易。当编写响应事件的代码时,无须关心Windows如何工作。反过来,如果编写代码

41、来改进与Windows的交互,您必须确切地知道Windows是如何工作的。回调是Windows中很重要的一部分。在Delphi尚未命名为Delphi之前,它就已经支持过程类型了。大约七到八年前,Object Pascal(Delphi)还被称为Turbo Pascal;因此它支持动态过程类型至少有十年之久了。6.4 过程类型中的默认参数值在过程类型中,可以定义参数的默认值。事实上,过程类型的定义并未改变默认参数值的语法,仍然与所有的过程定义都相同。如果察看一下帮助中的Object Pascal Grammar,很显然过程类型定义的标准规则中包括了与过程定义相同的子规则FunctionHeadi

42、ng和ProcedureHeading(依赖于过程类型的种类)。要包括参数的默认值,只需在参数类型之后添加常数表达式即可。type TDefaultParams = Procedure( const S : String = 'Default' )of object;注意:如果为某个过程提供了一个默认参数,并将该过程赋值给定义了默认参数值的过程类型变量,则参数将使用定义在过程类型中的默认值而不是定义在实际过程中的默认值。警告:可以将有默认参数的过程赋值给没有定义默认值的过程类型变量,但对该变量将无法使用默认参数。否则编译将出错。上面定义了一个以TDefaultParams为别名

43、的方法指针类型,参数为常量字符串类型,默认值为Default。可以声明TDefaultParams类型的变量,并将方法赋值给这些变量。对应的方法的原型必须与TDefaultParams相同,但参数不需要默认值。6.5 传递过程类型的参数使用过程类型的参数,需要定义相应的过程类型。编译器不能分析内嵌的过程类型定义,例如Procedure P(P : Procedure (I : Integer);。必须先定义过程类型,然后将该别名或新类型作为参数的类型。type TProcedure = Procedure( I : Integer);/ .Procedure P( Proc : TProced

44、ure );上面的例子对Procedure(I : Integer);使用了别名TProcedure。如果要定义新类型,在等号右侧使用关键字type即可,但实际传递的参数必须与该类型精确匹配。假定有过程Procedure Foo( I : Integer );,就可以用参数Foo来调用过程P,如下所示。P( Foo );但如果要把TProcedure作为新类型定义,则需要按两步进行。type TProcedure = Procedure( I : Integer );type TNewProcedure = type TProcedure;提示:如果试图在一个语句中定义过程类型和新类型,编译器

45、将给出错误提示“identifier expected but PROCEDURE found”(在beta版的编译器中可能出错,但Delphi 6发布时该问题将得到解决)。然后需要把Foo赋值给TProcedure类型的变量,并将该变量传递给P。第5章中提到过,如果类型为新类型(即在类型定义的右侧使用type关键字),编译器将对参数进行严格的类型检查。type TDummyProcedure = Procedure(I : Integer);type TProcedure = type TDummyProcedure;Procedure P( Proc : TProcedure );/ .P

46、rocedure Foo( I : Integer );begin/ some codeend;/ .varAProc : TProcedure;beginAProc := Foo;P( AProc );end;列出的代码中结尾处的块语句示范了如何将过程赋值给类型为TProcedure类型的变量。当过程类型定义为新类型时,这一点是必须的。6.6 过程类型常量当定义类型后,就可以像其他类型一样使用。可以声明该类型的变量、常数或创建新的子类型。const MyNowProc : Function : TDateTime = SysUtils.Now;上面列出的代码声明了过程类型常数MyNowPro

47、c,它指向返回TDateTime的函数,其值为SysUtils.pas中定义的Now函数。在可以使用其他类型常量的地方,也可以使用过程类型常量,如:创建静态本地变量的等价物,或定义可修改的过程常数等。6.7 事件处理程序事件处理程序是设计用来响应Windows消息的过程。在Delphi中,事件处理程序大多数是在双击事件特性时创建的。事件特性是过程类型的特性,它的值是Delphi所创建的事件处理程序。在分配代码来响应Windows和Delphi内部所产生的消息的途径中,这种定义事件处理程序的方式是最直接的。事件并不限于在Object Inspector中所列出的那些,也不必以指定的方式来使用事件

48、。动态实例化组件或创建自定义组件,是另一个需要定义自己的事件特性并编写处理程序的常见场合。在编写事件处理程序的代码时,有几个重要的准则需要记住。1一个事件处理程序可能会分配给多于一个的事件特性。在控件上拖动鼠标指针来选定控件(或按下Shift键并分别单击每个控件),双击事件特性编辑域来创建事件处理程序(如图6.2所示,可以看到当有多于一个的组件被选定时,Object Inspector的反应)。图6.2当多个组件被选定时,在Object Inspector 顶部的对象选 择器中会显示选定对象的数目(图中有两个对象被选定)2避免在事件处理程序中直接编写代码,而是调用一个方法来完成实际的工作。这样

49、在其他环境中需要该行为时就不必像OnClick(Nil)一样直接调用事件处理程序,因而使得代码更加可读。3当多于一个组件共享同一事件特性时,可使用Sender参数来判断哪个组件实际触发了事件处理程序。遵循这些步骤,可以提高代码的可读性和可扩展性。Button1Click中包含了较多的代码,这就不如调用一个命名得很好的过程那样有意义。6.7.1 定义事件处理程序事件处理程序是一个方法。为把事件处理程序分配给特定的事件特性,该方法与所处理的事件的参数数目、顺序和类型必须是相同的。例如,OnClick事件类型为TNotifyEvent。在Delphi帮助中查找TNotifyEvent,它定义为一个过

50、程,有一个名为Sender的TObject类型的参数。提示:对于预定义的事件,查看事件特性的过程类型定义并将其复制即可得到其参数列表。type TNotifyEvent = Procedure(Sender : TObject ) of Object;of Object限定符意味着该过程是类的成员。假定有类TForm1,它的OnClick事件的可能的事件处理程序如下。TForm1 = class(TForm)protectedProcedure OnClick( Sender : TObject );public/ end;把OnClick过程赋值给任何定义为TNotifyEvent的事件特性

51、都可以编译通过,而且在技术上这也是正确的。Button1.OnClick := OnClick;假定Button1是TForm的成员,则前面的代码是正确的。在技术上,把OnClick赋值给任何TNotifyEvent类型的事件特性都是正确的,但如果该事件并非Click事件,将导致语义上的错误。惯例是使用On作为事件处理程序的前缀,并用表示动作的词来描述该事件。6.7.2 调用事件方法调用事件方法的惟一禁令是出于风格方面的考虑。假定一些代码要使用事件处理程序OnClick( Sender : TObject ),可以调用OnClick(Nil)或OnClick(Self)来触发该行为。在风格上,

52、定义一个提供OnClick行为的方法则较为可取。考虑对于菜单的exit命令的OnClick方法,其中exit将结束应用程序。Procedure TFormMain.Exit1Click( Sender : TObject );begin/ cleanupApplication.Terminate;end;上面的代码执行了应用程序的清除工作并结束程序。在风格上,下面列出的代码更为可取。Procedure TFormMain.TerminateApplication;begin/ cleanupApplication.Terminate;end;Procedure TFormMain.Exit1C

53、lick( Sender : TObject );beginTerminateApplication;end;注意:当使用UML建模,特别是使用序列图时,如果事件处理程序出现在序列中,则显然事件处理程序中包含了该对象的可定义的行为;例如,当演示应用程序结束的序列到达需要结束行为的点时,Exit1Click就会出现。试着运转序列,如果存在某种行为但没有定义方法时,通常表示该类是不完全或不足够的。这只是个例子,利用正向和逆向工程以确保定义的类是完全并足够的。现在类的行为是自明的,在其他的上下文中无须通过事件处理程序即可调用TerminateApplication行为。6.7.3 触发事件当需要产生

54、事件时,可以调用分配给事件特性的过程。在6.1节“赢得对意大利细面条的战争”中,可利用事件处理程序简化主窗体与显示进度条的窗体之间的关系。unit UFormMain;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls,Forms, Dialogs,StdCtrls;typeTForm1 = class(TForm)Button1: TButton;procedure Button1Click(Sender: TObject);private Private declarations FCanceled :

55、 Boolean;Procedure OnCancel( Sender : TObject );Procedure Cancel;Procedure Process;public Public declarations end;varForm1: TForm1;implementationuses UFormProgress;$R *.DFMprocedure TForm1.Button1Click(Sender: TObject);beginProcess;end;procedure TForm1.Cancel;beginFCanceled := True;end;procedure TFo

56、rm1.OnCancel(Sender: TObject);beginCancel;end;procedure TForm1.Process;typeTRange = 1.10;Function PercentComplete( I : TRange ) : Double;beginresult := I / High(TRange);end;varI : Integer;beginFormProgress := TFormProgress.Create(Self);FormProgress.Show;FormProgress.OnCancel := OnCancel;tryfor I :=

57、Low(TRange) to High(TRange) dobeginif( FCanceled ) then break;FormProgress.UpdateProgress( PercentComplete(I) );Application.ProcessMessages;Sleep( 300 );end;finallyFormProgress.Free;end;end;end.unit UFormProgress;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls,Forms, Dialogs,ComCtrls, StdCtrls;typeTFormProgress = cla

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论