




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第 6 章接口的奥秘消息处理程序、过程类型以及处理程序把 Delphi 程序与 Windows 操作系统在一起。这就是说程序是嵌入到 Windows 中的。Windows 不能也无法预知运行在其中的程序,因为当 Windows 出现时, 大部分应用软件还没有编写出来。当 Windows 出现时,它必须提供一种途径,使得应用程序可以响应操作系统。最后的结果是:Windows 成为了一个基于消息的操作系统,而 Windows 程序必须响应这些消息。我将其称之为邮政服务式体系结构。最重要的是:通过响应消息,Windows 应用程序只需与 Windows 系统松散耦合。所有的 Windows 程序都必
2、须响应消息,而 Delphi 程序对此尤为出色。Windows 程序必须可以与任何程序通讯,而无须预先知道该特定程序所响应的消息和响应的方式。仿照 Delphi 中使用过程类型、特性和处理程序的方式,就可以隐藏 Windows 笨重的消息和驱动体系结构,并不同的 Windows消息和消息。实际上,也就是用通常的 Pascal 过程来Windows 消息处理程序和消息。本章讨论了消息处理程序、过程类型和处理程序,它们在用 Delphi 编写的 Windows 程序中随处可见。由于这些技术有助于使您的程序成为整洁、健壮、出色工作的关的内容。子系统,本章中完整地涵盖了有6.1赢得对意大利细面条的所谓
3、的意大利细面条式代码,指的是耦合代码。在避免耦合代码这一点上,基本上每个人都是口惠而实不至,因此代码很容易出现耦合。赢得的关键策略是,采用所有可能避免耦合代码的技术,并使这些技术成为根深蒂固的习惯。要做到这一点,您有必要了解一些术语,它们有助于维护模块的分离、从而不至于成为相互依赖的大块耦合代码。这里有一个代码有害的例子,您可能以前见到过。,unit Unit1; interface usesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls;typeTForm1 = class(T
4、Form) Button1: TButton;procedure Button1Click(Sender: TObject); private Private declarations public Public declarations Canceled : Boolean; Procedure Process;end; varForm1: TForm1;implementationuses Unit2;第 6 章 接口的奥秘136$R *.DFMprocedure TForm1.Button1Click(Sender: TObject); beginProcess;end;procedur
5、e TForm1.Process; varI : Integer; beginCanceled := False;Form2 := TForm2.Create(Self); tryForm2.Show;for I := 1 to 10 do beginif( Canceled ) then break;Sleep( 1000 ); / simulates some processing Form2.ProgressBar1.Position := Trunc(I *Form2.ProgressBar1.Max / 10); Application.ProcessMessages;end; fi
6、nallyForm2.Free; end;end;end.unit Unit2; interface usesWindows, Messages, SysUtils, Classes, Graphics, Forms, Dialogs,StdCtrls, ComCtrls;Controls,typeTForm2 = class(TForm) Button1: TButton; ProgressBar1: TProgressBar;procedure Button1Click(Sender: TObject); private Private declarations public Public
7、 declarations end;varForm2: TForm2;implementation uses Unit1;$R *.DFMprocedure TForm2.Button1Click(Sender: TObject);第 6 章 接口的奥秘137beginForm1.Canceled := True; end;end.在列出的代码中,Form1 模拟了主窗体,其中包含了过程 Process 的代码。该类定义了名为 Canceled的公有布尔类型成员。在 Form1 中对 Form2 进行了实例化。对 Sleep 的调用模拟了 Form1 中可能进行的处理过程。Form1 直接修改
8、了 Form2 中的进度条(见图 6.1)。处理将一直进行,直至所有的项都处理完毕或者 Canceled 被设置为 True。在 Form2 的 Button1Click必须很清楚地知道 Form2 的存在,反之亦然,二者之间的处理程序中修改了 Canceled 特性。结果是,Form1非常紧密(很清楚,二者是互相“了解”的,因为两个单元的实现部分的 uses 子句进行了相互)。图 6.1 负责进度条更新的窗体当程序的规模十分有限时,这种类型的代码很容易被忽视。不幸的是,有用的软件很少是简单的。如果两个窗体中有一个变为框组件,问题就更糟糕了。考虑 Form2 变成框组件的情况(框组件的信息请参
9、见第 10 章)。进一步可以认为 Form1 是某个复杂应用程序的主窗体,因此它拥有该应用程序中大多数的其他窗体。把 Form2 添加到 VCL 中(它是个组件,我们可以这样做),您的整个应用程序就都添加到了 VCL 中(不要笑,确实如此)。最后的结果形成了一个臃肿的 VCL 库,它依赖于非 VCL 的代码。当您编译应用程序时,VCL 代码也会重新进行编译。当您建立 VCL 库时,您的应用程序也将被编译。如果出了错,组件就无法建立因而也无法装载。真是一团糟。如果还要试着给其他开发者讲述这乱成一团的代码、其工作原理、以及如何对其进行修改等,那简直是代价高昂而且人性的行为。注意:公平地讲,必须提到
10、定义接口会引入另一种复杂性。这与孰劣的问题颇为相似。通过定义接口,可以使您更快地写出付款和延期付款孰优的代码。而当代码模块之间相互关系的数目和复杂程度已经难于时,试图解决问题可能为时已晚,因此,通过精确的接口提高代码的质量是更为可取的做法。即使对于非常简单的应用程序,定义接口的方法也可以得到非常好的效果。不幸的是,这种方法需要进行训练。程序员和审阅者需要注意并经常修改代码之间的关系,并对其提出一些简化方案。从长远看来,将解决问题的时机拖后代价要更高,等问题拖到了不能不解决的时候,可能就太晚了。窗体或数据模块之外的紧耦合代码,同样会导致问题。两个并非窗体的类也会产生前面的代码中的问题,如果在项目
11、中很晚才发现这种行为,将使的完成期限和交付产生严重的问题。如果 Form1 使用并显示 Form2,那么很清楚,Form1 与 Form2 之间的关系一种是拥有性质:Form1 拥有 Form2。当打破类的边界时, 程序的复杂性就会增加。上面的例子中,出现了两处这样的实例: Form1了Form2.ProgressBar1.Position,而 Form2了 Form1.Canceled.Form2.ProgressBar1.Position,这就是代码质量很差的所在,因为它打破了两个对象 Form1 和 ProgressBar1 的边界。如果改变状态的实现方式,则Form1 和 Form2
12、都需要进行改变。改动是代价昂贵的。本章的其余部分将提出一些策略,在更高的层次上向您提供与 Delphi 的高级术语密切相关的知识,这将有助于您写出更为了代码演化时的麻烦。、修改更少的代码,并且减少6.2类定义实用指南在学校里,主要的课程可能是有关语法与数据结构的。很少有大学和学院会讲授一个好的程序的组成要素,因为其过于。如果您确实对此有所了解,那可能是投入大量时间的和几经碰壁之后得到的。第 6 章 接口的奥秘138事实上,无须碰壁就可以学到一些好习惯。程序设计已经出现很多年了,许多程序员也编了很多年的程序, 有一些好的惯例已经牢固的确立。遵循这些惯例,即可编出一些出色的程序。Delphi 就是
13、这些程序之一。Delphi 的源代码是软件业的迈克尔乔丹。当然,向 Delphi 的源代码学习需要阅读很多代码。Delphi 的源代码并非有关知识的惟一来源。可以找到大量相关的例子和指南,但并不存在惟一的知识来源,即所谓“最好的惯例手册”。一些思想仍然被认为令人讨厌、过于。但确实有一些惯例被无争议的采用,它们将有助于您编写出更好的代码。6.2.1类中有什么有大约半打的最好的惯例,可有助于定义好的类。1由 Booch 的书(Booch,第 137 页)可知,类应该是简单的。这意味着基本上没有公限的属性。在 Delphi 的类中,大有半打左右的方法和特性。2要使公限的接口尽早稳定,并使其数量尽可能
14、少,这样程序员可以从类的简单行为建立较为高级的行为。3某个类都有一个可识别的主要函数。4将数据封装在私限的接口中,通过公有特性和支持特性的方法进行。5通过子类化来扩展类的行为,减少对已存在代码的影响,最小化重新测试的可能性。6要明白,第一次就得到最好的抽象模型可能很对抽象模型进行修改。当理解了有关问题域的信息后,要准备好一个程序员曾表示,在单一的应用程序中出现几个类表明对程序设计缺乏基本的理解。很显然,这是错误的观点。从拉丁名言“分而治之”可知,反过来才是对的。通常,错误的抽象模型或缺乏抽象模型证明对面向对象程序设计缺乏基础知识。注意:断言代码质量的陈述是基于所谓的意见。诸如“这就是我们在 X
15、YZ 公司做事的方法,因此它是最好的”之类的陈述。这是孤立工作的程序员的通病。关于什么是最好的惯例有许多断言,但其中大部分都指出对大量经验证据进行仔细思考后才能作出精确而科学的发现。例如在 Booch 的书中提到,对质量的定性度量是基于耦合、内聚、充足性、完全性以及原子性等(Booch,1994)。而缺乏经验或支持信息的纯粹论断,是非常值得怀疑的。应用“分而治之”的告诫,我们应把复杂的问题分解为一系列简单而基本的问题,并分别解决某个问题。本节开头的指南较为通用,可成为很好的起点。如果在类的公有接口部分定义了很多属性,代码会变得更复杂,反过来会影响您或其他程序员对代码的能力。6.2.2没有数据的
16、类许多规则都有例外。一般的,如果类定义中没有数据,按照通常的规则应把该类合并到其他类中,因为这种没有数据的类形式并未捕捉到问题域的完全的抽象。有一种类是该规则的例外,可称之为工具类。如果类的属性都是方法,则通常将其定义为类方法,这样无论有无对象实例均可使用方法。提示:按照通常的规则,类应有数据和方法。数据要时,才可以背离该规则。状态而方法定义行为。只有在确实需在 Delphi 中,TObject 类是所有类的基类。它包含了几个类方法,通常只在其子类实例化时才间接地创建该类的实例。对于所有类都应有数据的通常规则,TObject 就是个例外,定义该类是为了使所有的 Delphi类都具有某些基本能力
17、,有助于它们在 Delphi 程序中发挥作用。6.2.3命名惯例Delphi 并未强加任何令人难以忍受名惯例。Delphi 开发者所使用的一些惯例是基于规则的,而不是基于需要记忆的前缀,您可以自由选择是否遵从该惯例。但如果您试过,可能会认为它们是易于使用的,而且简化了编程。方法名惯例第 6 章 接口的奥秘139方法是动词与名词的组合。动词描述了动作,并且在名词之前,而名词则描述动作所施行的对象。我们还知道名词与动词合起来,足以明确表达一个完整的口语或有很高的可读性。的句子。因此名词与动词的名字具按照规则,要把方法的作用域限制到方法名中的动作和主题范围之内。如果在方法名中只有一个名词,那么您可能
18、是在处理特性。按照惯例,特性方法中读方法的前缀为动词 Get,而写方法的前缀为动词 Set,其后紧接着特性名(高级特性编程的信息请参见第 8 章)。处理程序名惯例Delphi 使用介词 On 作为处理程序的前缀。On 描述了动作或运动,如 OnClick 或 OnDragDrop。通过遵循一些惯例,几乎不需要花费时间即可找到方法、主题可以帮助您为代码命名。或特性的名字。术语的类型、动作和动作的数据名惯例Delphi 中的数据属性称之为字段。按照惯例,私有字段的前缀为 F。去掉 F,即可得到表示实际字段的特性的很方便的名字。请记住过程类型,即配很简单,将字段名去掉 F 前缀即可。和数据也可以是字
19、段,因此前缀为 F。将字段与特性匹前面提到过,基于规则的惯例可以使得代码在外观上一致而可靠,并可以减轻命名时想方设法的烦恼。遵守 Delphi名惯例与否,是您个人的选择。推荐您使用一种可辨别的风格,并一直坚持使用。消息处理程序名惯例消息处理程序是一些特别的方法,用于响应 Delphi 所实现的消息分发模型。按照规则,消息处理程序与其所响应的消息名字大致相同。许多 Windows 消息的前缀为 WM_,而 Delphi 对消息方法名的前两个字符使用了 WM。与特定的控件相关的消息的前缀也具有特别的前缀,例如前缀为 CB_的组合框。在 messages.pas 中可以找到这些消息的名字,它们被定义
20、为常数。6.2.4存取限定符的使用Delphi 帮助文档中称存取限定符定义了成员的可见性。这有点用词不当,因为存取限定符并非限制代码的可见性,而是限制代码的可性。存取限定符将代码划分为四种不同的可层次和大体上三个可区域。公有和公开区域指明了类的用户可以而且应该关心的那部分代码。保护权限指明了扩展一个类时,除了公码。限的代码之外,还需要注意的代码,最后,私限表示只有作者才会看到的代仔细而适中地将代码分布在不同的区域中,对于预期的用户可提高类所发挥的效用。对于公有访问权限来说,越少越好。要维持对公有属性的紧密,以确保您的类可以通过代码质量的原子性度量。6.2.5默认的公开或公限默认情况下,所有的属
21、性都具有公限,这与 C+并不相同,在 C+中属性在默认情况下是私有的。如果类编译时添加了运行时类型信息(在$M+状态下编译),如 TPersistent 类及其所有的子类,则所有属性默认情况下具有公开权限。当在工程中创建一个新的窗体时,所有的组件都出现在窗体定义的上部。警告:最好由 Delphi 来管理位于窗体类上部和.DFM 文件中的那些属性。如果您希望来做,也可以手工进行管理,但需要非常。Delphi 的行为是一致的,它并未把在窗体上绘制的控件与其他类区来,即使对于可以从.DFM 文件中读写窗体定义并使用消息来自动实例化组件的流类也是如此。考虑本章开头例子中的进度条窗体。该窗体中有一个 T
22、ProgressBar 和一个 TButton 对象。Delphi 对这些组件流化了足够的消息,以便在创建 Form2 的实例时自动创建这些组件。object Form2: TForm2 Left = 441Top = 222第 6 章接口的奥秘140Width = 263Height = 163Caption = 'Progress' Color = clBtnFaceFont.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13Font.Name = 'MS Sans Serif&
23、#39; Font.Style = OldCreateOrder = False PixelsPerInch = 120TextHeight = 16object Button1: TButton Left = 88Top = 88Width = 75Height = 25 Caption = 'Cancel' TabOrder = 0OnClick = Button1Click endobject ProgressBar1: TProgressBar Left = 24Top = 24Width = 217Height = 25Min = 0Max = 100TabOrder
24、 = 1 endend当读到 object 语句时(上面列出的.DFM 文件中有三个),Delphi 将从object 语句一行对象的类,创建该对象的实例,并读入其余的属性,这就是 DefineProperties 方法的任务。由于组件有构造函数和析构函数,因此您可以选择在设计时将其添加到应用程序中或在运行时动态地创建它们。在任何 TWinControl 控件上,都可以动态地创建并初始化控件。TWinControl 控件可以拥有控件。要在窗体上动态地创建 TButton 控件,可使用下面的代码,其中 Self 参数代表该窗体。with TButton.Create(Self) do begin
25、Parent := Self;Name := 'ButtonProcess'OnClick := SetBounds( Caption :=end;Button1Click;10, 10, Width, Height ); 'Process'注意:TWinControl 类维护了一个 TControl 的列表,由 TWinControl 所拥有的子控件组成。因此,虽然并未显式保存对控件列表中动态创建对象的,通过搜索控件列表来查找名为ButtonProcess 的 TButton 控件即可得到该。上面的代码模拟了 Delphi 在读入文件并创建窗体时的行为。上面列
26、出的代码与设计时添加按钮的区别在于,上面的代码添加了一个按钮到窗体,但并未在 DFM 文件中维护对该按钮的,而设计时也第 6 章 接口的奥秘141无法动态生成的按钮。6.2.6公开接口当使用 published 存取限定符时,表示该属性将出现在 Object Inspector 中。除此之外它与公有限是相同的。把方法放在公开部分是没有意义的,这与将其放在公有部分效果相同。权当定义既可在设计时又可在运行时修改的组件特性时,可将其权限定义为公开权限。如果您不需要在设计时修改特性,则无须将其定义为公开权限。特性也可以是公开的,组件公开的特性会在Object Inspector 的 Events 属性
27、页中列出。Delphi 6 中新增了公开对象特性的概念。在 Delphi 的早期版本中,如果创建包含其他组件的组件,只能使用属性提升的来内部对象的特性。例如,如果要在设计时修改框控件上的图像,则只能向该组件添加一个图像特性,然后利用图像特性的方法来实际的TImage 控件的 Picture 特性。现在您可以把 TImage 控件作为公开特性,并直接其 picture 特性。这是对公开区域的一个很好的改进(对象特性的知识及相应实例,请参见第 10 章)。6.2.7公有接口公有接口是类怎样使用的决定性因素。如果无法利用公有接口达到目的,那么也就没有使用该类的必要。按照 Booch 对代码质量的测量
28、标准,公有接口中的属性应该足够、完全、并具有原子性。足够指的是该类足以解决问题。即公有接口应是自给自足的。例如,一个文件流类中有写功能,要达到足够并完全, 需要在该类中添加兼容的读功能。原子性指的是类的功能应是基本的,如果一个类方法建立在另一个公有方法基础之上,则第二个方法不是原子性的,应从类中去掉。应使公有接口保持简明,并尽早地定义类的公有接口并使之稳定。如果在一个类的公有接口的基础上建立了其他类,则修改该类也会导致对依赖于它的类的修改。类中非公有的属性都应该是保护或私有的。6.2.8保护接口在面向对象中,保护接口供扩展该类的程序员使用。扩展意味着从已有的类派生子类,扩展其行为和状态。把属性
29、放在保护接口中时,对类进行扩展的程序员就可以修改这些属性。像古老的格言所说“可以做的事都会有人做”,如果您不希望在子类中改变方法或直接属性,那就使用私有接口吧。6.2.9私有接口私有接口与公有接口是并列的。从外部看来,只有类的基本行为和状态的属性才可以放在公有接口中,而私有接口好比是清洁工具柜。除非有意地使扩展该类的程序员可以它们,否则一切用于实现类的公有行为的属性都会放在私有接口中。注意:在私有与保护权限之间进行选择是的,因为这几乎与预先其他开发者然后使用您的代码一样,都是不可能的。如果您的用户是一些特定的开发者,例如组件的作者,可以考虑规则,通过将的数据和方法设置为保护权限,从而使代码的扩
30、展性更好。这也会使扩展您的组件的开发者有的灵活性。请把类的所有实现细节放在私有区域中。这样做可以清楚地向用户表明,他们无须关心这些项,因此可以使得您的类更易于使用。在定义类时,可以最后解决私有实现细节,因为在类改动时,它们对用户的影响最小。6.3创建自定义过程类型Delphi 和C+支持函数指针。而 Visual Basic 和 Java 则不支持。函数指针是个强的概念,它在过程一级提供了一个额外的动态层次。我们提到过,可以把回调过程传递给 Windows,这样就能让操作系统来调用回调过程。C+中的函数指针例子如下:#include "stdafx.h" #include
31、<iostream.h>void (*fp)();第 6 章 接口的奥秘142void Function()cout << "Hello World!" << endl;int main(int argc, char* argv)fp = Function; fp();return 0;void (*fp)();一行定义了一个名为 fp 的函数指针变量。通过把过程 Function 的名字赋值给 fp,就可以通过 fp 调用 Function。函数指针是很高级的概念,它向程序员提供了额外的实现选项(例子请参见 6.3.2 节“回调过程”)
32、。Delphi 对函数指针的支持不那么难懂。函数指针的概念在 Delphi 中称之为过程类型。过程类型与其他类型的在外观上是一致的,无须像 C+中那样进行间接。使用过程类型,可以编写支持 Windows回调过程、动态过程参数和处理程序的代码。它们的共同特征就是可以编写出动态和富于表达力的代码,而且难于在任何其他的语言中。6.3.1定义过程类型当定义过程类型时,可以将该类型作为别名引入,也可以将其作为新的类型引入,对于后者,编译器将进行强类型检查;该类型可以指定为方法或非方法的过程类型。过程类型简单的过程类型定义在类型部分。与前面的 C+代码一致的过程类型可type TProcedure = P
33、rocedure;。上面的意味着,任何没有参数的过程都可以赋值给 TProcedure 类型的变量。如果要有参数的过程类型,可以像过程那样包括参数列表,只需去掉过程名既可。typeTIntegerProcedure = Procedure( I : Integer); TObjectProcedure = Procedure( Sender : TObject ); TManyParams = Procedure( S : String; I : Integer );过程类型定义的参数列表可以与过程一样多变。可以包括数组参数、变量列表、常数和输出参数。决定性因素是您的需求。所需的过程的类型将决
34、定您所需要的过程类型。函数类型在对应于过程和函数的过程类型之间,不存在实际的区别。定义语句也是相同的,除了用关键字Function 代替 Procedure 外,在定义语句的末尾添加返回类型即可。typeTFunction = Function : Integer;TStringFunction = Function( S: String ) : Boolean; TVarFunction = Function( var D : Double ): String;如同过程类型一样,函数类型的参数列表是由该类型所匹配。的函数的参数所决定的。返回类型也必须用于方法的过程类型过程类型也能方法,可以是
35、类的成员函数或成员过程。当定义方法指针,即用于方法的过程类型时,需要表明该过程类型指向类的成员。在类型定义的结尾用 of Object 标记即可。第 6 章 接口的奥秘143typeTFunctionMethod = Function : Integer of Object; TProcedureMethod = Procedure( var S : String ) of Object;除了定义结尾的 of Object 限定符以外,该类型定义与非方法的类型定义是相同的。在 Delphi 中遇到的过程类型通常是TNotifyEvent。特性的类型。最常用的方法指针类型是在 classes.p
36、as 中定义的type TNotifyEvent = procedure (Sender: TObject) of object;该类型用于许多特性,包括您会经常用到的 OnClick。6.3.2回调过程回调过程是这样一种过程,其地址被赋值给变量或作为参数传递,用于在指定的时间进行调用。Windows 和 Delphi 都支持回调。回调过程的最常见的用途是在对象和响应耦合点。例如,鼠标单击行为就是这样引入到 TControl 控件中的。的动作之间提供一个松散的procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTON
37、UP; procedure TControl.WMLButtonUp(var Message: TWMLButtonUp);begininherited;if csCaptureMouse in ControlStyle then MouseCapture := False; if csClicked in ControlState thenbeginExclude(FControlState, csClicked);if PtInRect(ClientRect, SmallPointToPoint(Message.Pos) then Click;end;DoMouseUp(Message,
38、mbLeft); end;procedure TControl.Click; begin Call OnClick if assigned and not equal to associated action's OnExecute. If associated action's OnExecute assigned then call it, otherwise, call OnClick. if Assigned(FOnClick) and (Action <> nil) and (FOnClick <> Action.OnExecute) then
39、 FOnClick(Self)else if not (csDesigning in ComponentState) and (ActionLink <> nil) then ActionLink.Executeelse if Assigned(FOnClick) then FOnClick(Self);end;TControl 类建立了的 WndProc 过程,作为对 Windows 消息的回调过程。当 WndProc 从 Windows收到 WM_LBUTTONUP 消息时,它通过在 TObject 中引入的 Dispatch 方法将消息分发到控件。Dispatch 调用上面列
40、出的 WMLButtonUp 消息方法。该消息方法确保了控件可以像设计的那样接收鼠标单击。if( csClicked in ControlState ) then如果控件接收到单击,则调用动态过程 Click。动态方法表会调用正确的 Click 方法。处理单击的方法还会检查 FOnClick处理程序是否指向有效的过程。第 6 章 接口的奥秘144if( Assigned(FOnClick) then FOnClick(Self);如果 Assigned(FOnClick)结果为 True,就会调用该过程。一个通情达理的人可能问的第一个问题会是,为什么需要绕这么多圈子才能知道发生了鼠标单击?只有
41、从某一角度看来,才是显然的。您确实不必这样做,因为所有的这些信息在日常的开发活动中是不可见的。Windows 程序设计在十年前被认为很。而现在所有的 Delphi 程序员都只需双击 Object Inspector中的 OnClick特性,然后填写方法体中的空白即可。Windows 消息机制的复杂性在封装后对于日常的程序员是不可见的,复杂性的隐藏使事情变得很容易。当编写响应的代码时,无须关心 Windows 如何工作。反过来,如果编写代码来改进与 Windows的交互,您必须确切地知道 Windows 是如何工作的。回调是 Windows 中很重要的一部分。在 Delphi 尚未命名为 Del
42、phi 之前,它就已经支持过程类型了。大约七到八年前,Object Pascal(Delphi)还被称为 TurboPascal;因此它支持动态过程类型至少有十年之久了。6.4过程类型中的默认参数值在过程类型中,可以定义参数的默认值。事实上,过程类型的定义并未改变默认参数值的语法,仍然与所有的过程定义都相同。如果察看一下帮助中的 Object Pascal Grammar,很显然过程类型定义的标准规则中包括了与过程定义相同的子规则 FunctionHeading 和 ProcedureHeading(依赖于过程类型的种类)。要包括参数的默认值,只需在参数类型之后添加常数表达式即可。type T
43、DefaultParams = Procedure( const S : String = 'Default' ) of object;注意:如果为某个过程提供了一个默认参数,并将该过程赋值给定义了默认参数值的过程类型变量,则参数将使用定义在过程类型中的默认值而不是定义在实际过程中的默认值。警告:可以将有默认参数的过程赋值给没有定义默认值的过程类型变量,但对该变量将无法使用默认参数。否则编译将出错。上面定义了一个以 TDefaultParams 为别名的方法指针类型,参数为常量字符串类型,默认值为Default。可以TDefaultParams 类型的变量,并将方法赋值给这些变
44、量。对应的方法的原型必须与TDefaultParams 相同,但参数不需要默认值。6.5传递过程类型的参数使用过程类型的参数,需要定义相应的过程类型。编译器不能分析内嵌的过程类型定义,例如 ProcedureP(P : Procedure (I : Integer);。必须先定义过程类型,然后将该别名或新类型作为参数的类型。type TProcedure = Procedure( I : Integer);/ .Procedure P( Proc : TProcedure );上面的例子对 Procedure(I : Integer);使用了别名 TProcedure。如果要定义新类型,在等号
45、右侧使用关键字 type 即可,但实际传递的参数必须与该类型精确匹配。假定有过程 Procedure Foo( I : Integer );,就可以用参数 Foo 来调用过程 P,如下所示。P( Foo );但如果要把 TProcedure 作为新类型定义,则需要按两步进行。type TProcedure = Procedure( I : Integer );type TNewProcedure = type TProcedure;第 6 章 接口的奥秘145提示:如果试图在一个语句中定义过程类型和新类型,编译器将给出错误提示“identifier expected but PROCEDURE
46、 found”(在 beta 版的编译器中可能出错,但 Delphi 6 发布时该问题将得到解决)。然后需要把 Foo 赋值给 TProcedure 类型的变量,并将该变量传递给 P。第 5 章中提到过,如果类型为新类型(即在类型定义的右侧使用 type 关键字),编译器将对参数进行严格的类型检查。type TDummyProcedure = Procedure(I : Integer); type TProcedure = type TDummyProcedure; Procedure P( Proc : TProcedure );/ .Procedure Foo( I : Integer
47、); begin/ some code end;/ . varAProc : TProcedure; beginAProc := Foo; P( AProc );end;列出的代码中结尾处的块语句示范了如何将过程赋值给类型为 TProcedure 类型的变量。当过程类型定义为新类型时,这一点是必须的。6.6过程类型常量当定义类型后,就可以像其他类型一样使用。可以该类型的变量、常数或创建新的子类型。const MyNowProc : Function : TDateTime = SysUtils.Now;上面列出的代码了过程类型常数 MyNowProc,它指向返回 TDateTime 的函数,其
48、值为 SysUtils.pas中定义的 Now 函数。在可以使用其他类型常量的地方,也可以使用过程类型常量,如:创建静态本地变量的等价物,或定义可修改的过程常数等。6.7处理程序处理程序是设计用来响应 Windows 消息的过程。在 Delphi 中,件特性时创建的。特性是过程类型的特性,它的值是 Delphi 所创建的处理程序大多数是在双击事处理程序。在分配代码来响应 Windows 和 Delphi 内部所产生的消息的途径中,这种定义处理程序的方式是最直接的。并不限于在 Object Inspector 中所列出的那些,也不必以指定的方式来使用。动态实例化组件或创建自定义组件,是另一个需要
49、定义的的代码时,有几个重要的准则需要记住。特性并编写处理程序的常见场合。在编写处理程序1一个处理程序可能会分配给多于一个的特性。在控件上拖动鼠标指针来选定控件(或按下 Shift 键并分别单击每个控件),双击特性编辑域来创建处理程序(如图 6.2 所示,可以看到当有多于一个的组件被选定时,Object Inspector 的反应)。第 6 章 接口的奥秘146图 6.2 当多个组件被选定时,在Object Inspector 顶部的对象选择器中会显示选定对象的数目(图中有两个对象被选定)2避免在处理程序中直接编写代码,而是调用一个方法来完成实际的工作。这样在其他环境中需要该行为时就不必像 On
50、Click(Nil)一样直接调用处理程序,因而使得代码更加可读。3当多于一个组件共享同一序。特性时,可使用 Sender 参数来哪个组件实际触发了处理程遵循这些步骤,可以提高代码的可读性和可扩展性。Button1Click 中包含了较多的代码,这就不如调用一个命名得很好的过程那样有意义。6.7.1定义处理程序处理程序是一个方法。为把处理程序分配给特定的特性,该方法与所处理的的参数数目、顺序和类型必须是相同的。例如,OnClick类型为 TNotifyEvent。在 Delphi 帮助中查找TNotifyEvent,它定义为一个过程,有一个名为 Sender 的 TObject 类型的参数。提示
51、:对于预定义的,查看特性的过程类型定义并将其即可得到其参数列表。type TNotifyEvent = Procedure(Sender : TObject ) of Object;of Object 限定符意味着该过程是类的成员。假定有类 TForm1,它的 OnClick程序如下。的可能的处理TForm1 = class(TForm) protectedProcedure OnClick( Sender : TObject ); public/ end;把 OnClick 过程赋值给任何定义为 TNotifyEvent 的确的。特性都可以编译通过,而且在技术上这也是正Button1.OnC
52、lick := OnClick;假定 Button1 是 TForm 的成员,则前面的代码是正确的。在技术上,把 OnClick 赋值给任何 TNotifyEvent 类型的特性都是正确的,但如果该并非 Click,将导致语义上的错误。惯例是使用 On 作为6.7.2调用方法处理程序的前缀,并用表示动作的词来描述该。调用方法的惟一禁令是出于风格方面的考虑。假定一些代码要使用处理程序 OnClick( Sender :TObject ),可以调用 OnClick(Nil)或 OnClick(Self)来触发该行为。在风格上,定义一个提供 OnClick 行为的方法则较为可取。考虑对于菜单的 ex
53、it 命令的 OnClick 方法,其中 exit 将结束应用程序。Procedure TFormMain.Exit1Click( Sender : TObject );第 6 章 接口的奥秘147begin/ cleanup Application.Terminate;end;上面的代码执行了应用程序的清除工作并结束程序。在风格上,下面列出的代码更为可取。Procedure TFormMain.TerminateApplication; begin/ cleanup Application.Terminate;end;Procedure TFormMain.Exit1Click( Sende
54、r : TObject ); beginTerminateApplication;end;注意:当使用 UML 建模,特别是使用序列图时,如果处理程序出现在序列中,则显然事件处理程序中包含了该对象的可定义的行为;例如,当演示应用程序结束的序列到达需要结束行为的点时,Exit1Click 就会出现。试着运转序列,如果存在某种行为但没有定义方法时,通常表示该类是完全并足够的。全或不足够的。这只是个例子,利用正向和逆向工程以确保定义的类是处理程序即可调用 TerminateApplication现在类的行为是自明的,在其他的上下文中无须通过行为。6.7.3触发当需要产生时,可以调用分配给特性的过程。
55、在 6.1 节“赢得对意大利细面条的”中,可利用处理程序简化主窗体与显示进度条的窗体之间的关系。unit UFormMain; interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Forms, Dialogs,StdCtrls; typeTForm1 = class(TForm)Button1: TButton;Controls,procedure private Private FCanceled Procedure Procedure ProcedurepublicButton1Click(Sender: TObject);declarations : Boolean;OnCancel( Sender : TObject ); Cancel;Process; Public declarations end;varForm1: TForm1;implementationuses UFormProgress;第 6 章 接口的奥秘148$R *.DFMprocedure
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 中国阻燃面料项目创业计划书
- 2025年中国四溴苯酐二醇项目商业计划书
- 2025年中国摩擦材料用晶须项目创业计划书
- 2025年中国耐火浇注料项目商业计划书
- 党的二十考试题及答案
- 大专中医考试题目及答案
- XXXXXXXXXXXXXXXXXXXX东北民猪可行性研究报告
- 大学酶工程考试题及答案
- 2025参照公寓房屋抵押借款合同范本
- 畜牧证考试题目及答案
- 水汇休闲业务知识培训课件
- 基金从业人员资格模拟测试完美版带解析2025年含答案
- 2025-2030儿童财商培养纳入早期智力开发体系的社会接受度调研
- 2025内蒙古呼和浩特市总工会工会社会工作者、专职集体协商指导员招聘29人备考考试题库附答案解析
- 2025年下半年宝山区国有企业员工招聘笔试备考试题及答案解析
- 学堂在线 中国传统艺术-篆刻、书法、水墨画体验与欣赏 章节测试答案
- T/CCIAS 009-2023减盐酱油
- 流行性感冒诊疗方案(2025年版)权威解读课件
- 2017版油气田地面建设工程项目竣工验收手册20170227
- 中央空调维保方案
- 国家开放大学电大专科《政治学原理》论述题题库及答案
评论
0/150
提交评论