Delphi自定义组件(1)

来源:互联网 发布:dev c 编程 编辑:程序博客网 时间:2024/03/29 17:49
 

组件是Delphi应用程序的基本元素。尽管大多数组件代表用户界面的可见元素,但组件也可以是程序中的不可见元素,如数据库组件。在前面的学习中,我们已经能够根据程序功能需要,合理地选择和应用Delphi提供的组件,同时,Delphi中也允许用户自己定义组件,实现相关的功能。

8.4.1 组件基础知识

Delphi的组件都是VCL(Visual ComponentLibrary,可视组件类库)的对象继承树的一部分,图8.7列出组成VCL的对象的关系。TComponent是VCL中每一个组件的共同祖先。TComponent提供了Delphi组件正常工作的最基本的属性和事件。库中的各条分支提供了其他的更专一的功能。

为弄清什么是组件,可以从3个方面来考察它:功能定义、技术定义和经验定义。

1. 功能定义

从最终用户角度来讲,组件是在Component Palette上选择的,并在Form(窗体)设计窗口和Code Designer(代码)窗口中操作的元素。从组件编写者角度来讲,组件是代码中的对象。

在编写组件之前,应当相当熟悉已有的Delphi组件,这样才能使创建的组件适合用户的需要。编写组件的目标之一就是使组件在被用于编程时尽可能地类似于其他已经实现的组件。

图8.7 VCL对象继承树

2. 技术定义

从最简单的角度看,组件是任何从TComponent继承的对象。TComponent定义了所有组件必须的、最基本的行为。对于用户自定义的新组件,TComponent并不知如何处理它的具体功能,因此,用户必须自己描述它。

3. 经验定义

在实际编程中,组件是能插入Delphi开发环境的任何元素。它可能具有程序的各种复杂性,具有用户希望实现的各种功能。

8.4.2 新组件的定义

1. 建立新组件的理由

建立新组件一般有两个理由:一个是改变类型的默认情况,避免重复;另一个是为组件增加新的功能。

从避免重复的角度考虑,建立新组件是为了建立可重用对象。如果组件的功能设计合理,则从软件重用的角度考虑,能节省程序员大量重复性工作。

在程序设计中,避免不必要的重复是很重要的。如果发现在代码中一遍又一遍重写相同的代码,就应当考虑将代码放在子过程或函数中,或干脆建立一个函数库。设计组件也是这个道理,如果总是改变相同的属性或相同的方法调用,就应创建新组件。

创建新组件的另一个原因是想给已有的组件增加新的功能。你可以从已有组件直接继承(如Edit)或从抽象对象类型继承(如TComponent,TControl)。虽然能为组件增加新功能,但不能将原有组件的属性移走。

2. 建立组件的理论准备

当建立组件时,通过从对象树中已有的对象继承获得新对象,并将其加入VCL中。

建立组件和我们以前做的工作有很大的不同,在Delphi环境中建立组件和在应用程序中使用组件有3个重要差别:

(1)编写组件是非可视化的

编写组件与建立Delphi应用程序最明显的区别是,组件编写完全以代码的形式进行,即非可视化的。因为Delphi应用程序的可视化设计需要已完成的组件,而建立这些组件就需要用Object Pascal代码编写。

虽然你无法使用可视化工具来建立组件,但你能运用 Delphi开发环境的所有编程特性,如代码编辑器、集成化调试和对象浏览。

(2)编写组件需要深入的面向对象知识

除了非可视化编程之外,建立组件和使用它们的最大区别是:当建立新组件时,需要从已有组件中继承产生一个新对象类型,并增加新的属性和方法。而对于组件使用者,他们在建立Delphi应用程序时,只是使用已有组件,在设计阶段通过改变组件属性和描述响应事件的方法来定制它们的行为。

当通过继承产生一个新对象时,用户有权访问祖先对象中对最终用户不可见的部分。这些部分被称为Protected界面。在很大部分的实现上,后代对象也需要调用他们的祖先对象的方法,因此,编写组件者应相当熟悉面向对象编程的特性。

(3)编写组件要遵循更多的规则

编写组件过程比生成可视化应用程序采用更传统的编程方法。与使用已有组件相比,有更多的规则要遵循。在开始编写自己的组件之前,最重要的事莫过于熟练应用Delphi自带的组件,以得到对命名规则以及组件用户所期望功能等的直观认识。组件用户期望组件能做到的最重要的事情,莫过于他们在任何时候能对组件做任何事。编写满足这些期望的组件并不难,只要预先想到和遵循规则。

3. 编写组件的面向对象技术

组件使用者在Delphi开发环境中,将遇到包含数据和方法的对象。他们将在设计阶段和运行阶段操作对象,而编写组件将需要更多的关于对象的知识,因此,组件编写者应当十分熟悉Delphi的面向对象的程序设计。

我们首先要明确下列概念:

(1)组件是类

组件用户和组件编写者最基本的区别是用户处理对象的实例,而编写者创建新的对象类型。这个概念是面向对象程序设计的基础。例如,TButton对象是对象,而用户放置在窗体上的Button按钮只是TButton的一个实例。

(2)组件接口要利用类的域的知识

在前面的章节中,我们已经详细阐述了面向对象的基础——类。我们知道,Delphi语言为对象的各部分提供了5个级别的访问控制。访问控制定义什么代码能访问对象的哪一部分。通过描述访问级别,定义了组件的接口。如果合理安排接口,将提高组件的可用性和重用性。

除非特别描述,否则加在对象里的域、方法和属性的控制级别都是Published,这意味着任何代码可以访问整个对象。

(3)组件的方法实现

匹配这个概念是用来描述当调用方法时应用程序怎样决定执行什么样的代码。当编写调用对象的代码时,看上去与任何其他过程或函数调用没什么不同,但对象有4种不同的匹配方法。

这4种匹配方法的类型包括静态方法、虚拟方法、动态方法、抽象方法。

1)静态方法

如果没有特殊声明,所有的对象方法都是静态的。静态方法的工作方式与一般的过程和函数调用相同。在编译时,编译器决定方法地址,并与方法联接。

静态方法的优点是匹配相当快。因为由编译器决定方法的临时地址,并直接与方法相联。虚拟方法和动态方法则相反,用间接的方法在运行时查找方法的地址,这将花较长的时间。

静态方法的另一个特点是,当被另一类型继承时不做任何改变,这就是说如果你声明了一个包含静态方法的对象,然后从该对象继承新的对象,则该后代对象享有与祖先对象相同的方法地址,因此,不管实际对象是谁,静态方法都完成相同的工作。

不能覆盖静态方法,在后代对象中声明相同名称的静态方法将取代祖先对象方法。

2)虚拟方法

调用虚拟方法与调用任何其他方法一样,但匹配机制有所不同。虚拟方法支持在后代对象中重定义方法,但调用方法完全相同,虚拟方法的地址不是在编译时决定,而是在运行时才查找方法的地址。

为声明一个新的方法,在方法声明后增加virtual指令。方法声明中的virtual指令在对象VMT(Virtal Method Table,虚拟方法表)中创建一个入口,该虚拟方法表保存对象类所有虚拟方法的地址。

当你从已有对象获得新的对象,新对象得到自己的VMT,它包含所有的祖先对象的VMT入口,再增加在新对象中声明的虚拟方法。后代对象能覆盖任何继承的虚拟方法。

覆盖一个方法是扩展它,而不是取代它。后代对象可以重定义和重实现在祖先对象中声明的任何方法。但无法覆盖静态方法。覆盖一个方法,要在方法声明的结尾增加override指令。

3)动态方法

动态方法是稍微不同于虚拟方法的匹配机制。因为动态方法没有对象VMT的入口,它们减少了对象消耗的内存数量。匹配动态方法比匹配一般的虚拟方法慢。因此,如果方法调用很频繁,你最好将其定义为虚拟方法。

定义动态方法时,在方法声明后面增加dynamic指令。

与对象虚拟方法创建入口不同的是dynamic给方法赋了一数字,并存储相应代码的地址,动态方法列表只包含新加的和覆盖的方法入口,继承的动态方法的匹配是通过查找每一个祖先的动态方法表(按与继承“反转的顺序”),因此动态方法用于处理消息(包括Windows消息)。实际上,消息处理过程的匹配方式与动态方法相同,只是定义方法不同。

4)抽象方法

抽象方法利用关键字abstract实现,如果一个方法定义为抽象,则这个方法不需要在本类中实现。而在它的子类中,可以在重载方法中实现相关功能。

如果一个类的抽象方法没有被重载,则这个方法是不能被调用的,否则会产生调用抽象方法的内部错误。

原创粉丝点击