当前位置: 首页 > 文档资料 > Delphi 基础教程 >

第二章 Delphi面向对象的编程方法(四)

优质
小牛编辑
125浏览
2023-12-01
第二章 Delphi面向对象的编程方法(四)

2.1.10.7 将库单元加入工程

将库单元加入工程是比较简单的。无论是您自己建立的库单元还是Delphi建立的与窗体有关的库单元,如果已经完成,则先打开您想加入库单元的工程(可以用Open Project打开工程);再选用File|Open File,然后选择您想加入的源程序(.PAS文件),并选择OK即可。则库单元被加入到应用程序中。

2.2 用Delphi的对象进行编程

Delphi是基于面向对象编程的先进开发环境。面向对象的程序设计(OOP)是结构化语言的自然延伸。OOP的先进编程方法,会产生一个清晰而又容易扩展及维护的程序。一旦您为您的程序建立了一个对象,您和其他的程序员可以在其他的程序中使用这个对象,完全不必重新编制繁复的代码。对象的重复使用可以大大地节省开发时间,切实地提高您和其他人的工作效率。

2.2.1 什么是对象

一个对象是一个数据类型。对象就象记录一样,是一种数据结构。按最简单的理解,我们可以将对象理解成一个记录。但实际上,对象是一种定义不确切的术语,它常用来定义抽象的事务,是构成应用程序的项目,其内涵远比记录要丰富。在本书中,对象可被理解为可视化部件如按钮、标签、表等。

了解对象,最关键的是掌握对象的特性。一个对象,其最突出的特征有三个:封装性、继承性、多态性。

2.2.1.1 对象的封装性

对对象最基本的理解是把数据和代码组合在同一个结构中,这就是对象的封装特性。将对象的数据域封闭在对象的内部,使得外部程序必需而且只能使用正确的方法才能对要读写的数据域进行访问。封装性意味着数据和代码一起出现在同一结构中,如果需要的话,可以在数据周围砌上“围墙”,只有用对象类的方法才能在“围墙”上打开缺口。

2.2.1.2 对象的继承性

继承性的含义直接而且显然。它是指把一个新的对象定义成为已存在对象的后代;新对象继承了旧类的一切东西。在往新对象中添加任何新内容以前,父类的每一个字段和方法都已存在于子类中,父类是创建子类的基石。

2.2.1.3 对象的多态性

多态性是在对象体系中把设想和实现分开的手段。如果说继承性是系统的布局手段,多态性就是其功能实现的方法。多态性意味着某种概括的动作可以由特定的方式来实现,这取决于执行该动作的对象。多态性允许以类似的方式处理类体系中类似的对象。根据特定的任务,一个应用程序被分解成许多对象,多态性把高级设计处理的设想如新对象的创建、对象在屏幕上的重显、程序运行的其它抽象描述等,留给知道该如何完美的处理它们的对象去实现。

2.2.1.4 通过Delphi实例了解对象

让我们结合Delphi的实例讨论对象的概念:

当您要建立一个新工程时,Delphi 将显示一个窗体作为设计的基础。在程序编辑器中,Delphi将这个窗体说明为一个新的对象类型,并同时在与窗体相关联的库单元中生成了创建这个新窗体对象的程序代码。

unit Unit1;

interface

uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs;

type

TForm1 = class(TForm) {窗体的类型说明开始}

private

{ Private declarations }

public

{ Public declarations }

end; {窗体的类型说明结束}

var

Form1: TForm1; {说明一个窗体变量}

implementation

{$R *.DFM}

end.

新的窗体类型是TForm1,它是从TForm继承下来的一个对象。它具有对象的特征:含有域或方法。由于您未给窗体加入任何部件,所以它只有从TForm类中继承的域和方法,在窗体对象的类型说明中,您是看不到任何域、方法的说明的。Form1称为TForm1类型的实例(instance)。您可以说明多个对象类型的实例,例如在多文档界面(MDI)中管理多个子窗口时就要进行这样的说明。每一个实例都有自己的说明,但所有的实例却共用相同的代码。

假设您向窗体中加入了一个按钮部件,并对这个按钮建立了一个OnClick事件处理过程。再查看Unit1的源程序,会发现TForm1的类型说明部分如下:

type

TForm1 = class(TForm)

Button1: TButton;

procedure Button1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

现在TForm1对象有了一个名为Button1的域:它是您在窗体中加入的按钮。TButton是一个对象类型,Button1是Tbutton的一个实例。它被TForm1对象所包含,作为它的数据域。每当您在窗体中加入一个部件时,部件的名称就会作为TFom1的域加入到类型说明中来。在Delphi中,您所编写的事件处理过程都是窗体对象的方法。每当您建立一个事件处理过程,就会在窗体的对象类型中说明一个方法。

当您使用Object Inspector来改变对象(部件)的名称时,这个名称的改变会反映到程序中。例如,在Object Inspector中将Form1的Name属性命名为ColorBox,您会发现在类型说明部分,会将前文的TForm1改为:

TColorBox=class(TForm);

并且在变量说明部分,会说明ColorBox为TColorBox类型的变量,由Delphi自动产生的事件处理过程名称会自动改为TColorBox.Button1Click;但您自行编写的实现部分的代码却不会被自动修改。因此,如果您在改变Name属性前编写了程序,则您必须将事件处理过程中的对象名称进行改变。所以,原先的Form1.Color要改为ColorBox.Color。

2.2.2 从一个对象中继承数据和方法

前面的TForm1类型是很简单的,因为它只含有域Button1和方法Button1Click。但是在这个窗体上,您可以改变窗体的大小、加入或删除窗体的最大最小化按钮,或设置这个窗体为MDI界面。对于一个只包含一个域和方法的对象来讲,您并没有看到显式的支持程序。在窗体上单击鼠标或用Object Inspector的上端的Object Selector选中Form1对象,按动F1查阅它的在线帮助,您会在Properties和Method中找到它的继承到的全部属性和方法。这些是在TForm类型中说明的,TForm1是TForm的子类,直接继承了它所有的域、方法、属性和事件。例如窗体的颜色属性Color就是在TForm中说明的。当您在工程中加入一个新窗体时,就等于加入了一个基本模型。通过不断地在窗体中加入部件,您就自行定义了一个新的窗体。要自定义任何对象,您都将从已经存在的对象中继承域和方法,建立一个该种对象的子类。例如对象TForm1就被说明为对象TForm的子类,拥有一个窗体部件的基本属性或方法。只有当您在窗体中加入了部件或编写了事件处理过程时,Form1才成为您自己的类型。

一个比较特殊的对象是从一个范围较广或较一般的对象中继承下来的,它是这个特别对象的祖先,这个对象则称为祖先的后代。一个对象只能有一个直接的祖先,但是它可以有许多后代。TForm是TForm1类型的祖先,所有的窗体对象都是TForm的后代。

用F1查阅窗体的在线帮助时,您会发现TForm被称为component(部件)。这是因为所有的部件都是对象。

在这个结构中所有的部件都是对象。部件类型TComponent从TObject类型中继承数据和程序代码,并具有额外的可以用作特殊用途的属性、方法、事件,所以部件可以直接和用户打交道,记录它的状态并存贮到文件中等等。控制类型TControl从TComponent中继承而来,又增加了新的功能,如它可以显示一个对象。在上图中,虽然TCheckBox不是直接由TObject继承来的,但是它仍然有任何对象所拥有的属性,因为在VCL结构中,TCheckBox终究还是从TObject 中继承了所有功能的特殊对象,但它还有些自行定义的独到的功能,如可以选择记录状态等。

2.2.3 对象的范围

2.2.3.1 关于对象的范围

一个对象的范围决定了它的数据域、属性值、方法的活动范围和访问范围。在一个对象的说明部分说明的数据域、属性值、方法都只是在这个对象的范围中,而且只有这个对象和它的后代才能拥有它们。虽然这些方法的实际程序代码可能是在这个对象之外的程序库单元中,但这些方法仍然在这个对象的范围内,因为它们是在这个对象的说明部分中说明的。

当您在一个对象的事件处理过程中编写程序代码来访问这个对象的属性值、方法或域时,您不需要在这些标识符之前加上这个对象变量的名称。例如,如果您在一个新窗体上加入一个按钮和一个编辑框,并为这个按钮编写OnClick事件处理过程:

procedure TForm1.Button1Click(Sender:Tobject);

begin

Color :=clFuchsia;

Edit1.Color :=clLime;

end;

其中的第一行语句是为整个窗体Form1着色。您也可以编写如下:

Form1.Color :=clFuchsia;

但您可以不必加上Form1.,因为Button1Click方法是在TForm1对象的范围里。当您在一个对象的范围中时,您可以省略所有这个对象中的属性值、方法、域之前的对象标识符。但是当您编写第二个语句改变编辑框的底色时,因为此时您想访问的是TEdit1对象的Color属性,而不是TForm1类型的,所以您需要通过在属性前面加上编辑框的名称来指明Color属性值的范围。如果不指明,Delphi会象第一个语句一样,将窗体的颜色变成绿色。因为Edit1部件是在窗体中的,它是窗体的一个数据域,所以您同样不必指明其从属关系。

如果Edit1是在其他窗体中,那么您需要在编辑框之前加上这个船体对象的名称了。例如,如果Edit1是在Form2之中,那它是Form2说明的一个数据域,并位于Form2的范围中,那么您需要将第二句改为:

Form2.Edit1.Color := clLime;

而且需要把Unit2加入Unit1的uses子句中。

一个对象的范围扩展到这个对象的所有后代。TForm的所有属性值、方法和事件都在TForm1的范围中,因为TForm1是TForm的后代。您的应用程序不能说明和祖先的数据域重名的类型、变量等。如果Delphi显示了一个标识符被重复定义的信息,就有可能是一个数据域和其祖先对象(例如TForm)的一个数据域有了相同的名称。可以尝试改变这个标识符的名称。

2.2.3.2 重载一个方法

您可以重载(Override)一个方法。通过在后代对象中说明一个与祖先对象重名的方法,就可以重载一个方法。如果想使这个方法在后代对象中作和祖先对象中一样的工作但是使用不同的方式时,您就可以重载这个方法。Delphi不推荐您经常重载方法,除非您想建立一个新的部件。重载一个方法,Delphi编译器不会给出错误或警告提示信息。

2.2.4 对象公有域和私有域的说明

当使用Delphi的环境来建立应用程序时,您可以在一个TForm的后代对象中加入数据域和方法,也可以通过直接修改对象类型说明的方法来为一个对象加上域和方法,而不是把一个部件加入窗体或事件处理过程中。

您可以在对象的Public或Private部分加入新的数据域和方法。Public和Private是Object Pascal的保留字。当您在工程中加入新的窗体时,Delphi开始建立这个新窗体对象。每一个新的对象都包含public和private指示,以便您在代码中加入数据域和方法。在public部分中说明其它库单元中对象的方法也可以访问的数据域或方法。在private部分的说明有访问的限制。如果您在private中说明域和方法,那么它在说明这个对象的库单元外是不透明的,而且不能被访问。private中可以说明只能被本库单元方法访问的数据域和本库单元对象访问的方法。过程或函数的程序代码可以放在库单元的implementation部分。

2.2.5 访问对象的域和方法

当您想要改变一个窗体对象的一个域的某个属性,或是调用它的一个方法时,您必须在这个属性名称或调用方法之前加上这个对象的名称。例如,如果您的窗体上有一个编辑框部件,而您需要在运行中改变它的Text属性,需要编写下列的代码:

Edit1.Text := 'Welcome to Delphi';

同样,清除编辑框部件中选中的文本,可以调用TEdit部件的相应方法:

Edit1.ClearSelection;

如果您想改变一个窗体对象中一个对象域的多个属性或调用多个方法时,使用with语句可以简化您的程序。with语句在对象中可以和在记录中一样方便地使用。下面的事件处理过程在响应OnClick事件时,会对一个列表框作多个调整:

procedure TForm1.Button1Click(Sender:TObject);

begin

ListBox1.Clear;

ListBox1.MultiSelect :=True;

ListBox1.Item.Add('One');

ListBox1.Item.Add('Two');

ListBox1.Item.Add('Three');

ListBox1.Sorted :=Ture;

ListBox1.FontStyle :=[fsBold];

ListBox1.Font.Color :=clPurple;

ListBox1.Font.Name :='Times New Roman';

ListBox1.ScaleBy(125,100);

end;

如果使用了With语句,则程序如下:

procedure TForm1.Button1Click(Sender:TObject);

begin

with (ListBox1) do

begin

Clear;

MultiSelect :=True;

Item.Add('One');

Item.Add('Two');

Item.Add('Three');

Sorted :=Ture;

FontStyle :=[fsBold];

Font.Color :=clPurple;

Font.Name :='Times New Roman';

ScaleBy(125,100);

end;

end;

使用with语句,您不必在每一个属性或方法前加上ListBox1标识符,在With语句之内,所有的属性或调用方法对于ListBox这个对象而言都是在它的范围内的。

2.2.6 对象变量的赋值

如果两个变量类型相同或兼容,您可以把其中一个对象变量赋给另一个对象变量。例如,对象TForm1和TForm2都是从TForm继承下来的类型,而且Form1和Form2已被说明过,那么您可以把Form1赋给Form2:

Form2 :=Form1;

只要赋值的对象变量是被赋值的对象变量的祖先类型,您就可以将一个对象变量赋给另一个对象变量。例如,下面是一个TDataForm的类型说明,在变量说明部分一共说明了两个变量:AForm和DataForm。

type

TDataForm = class(TForm)

Button1:TButton;

Edit1:TEdit;

DataGrid1:TDataGrid;

Database1:TDatabase;

TableSet1:TTableSet;

VisibleSession1:TVisibleSession;

private

{私有域说明}

public

{公有域说明}

end;

var

AForm:TForm;

DataForm:TDataForm;

因为TDataForm是TForm类型的后代,所以Dataform是AForm的后代,因此下面的赋值语句是合法的:

AForm :=DataForm;

这一点在Delphi中是极为重要的。让我们来看一下应用程序调用事件处理过程的过程,下面是一个按钮部件的OnClick事件处理过程:

procedure TForm1.Button1Click(Sender:TObject);

begin

end;

您可以看到TObject类在Delphi的Visual Component Library的顶部,这就意味着所有的Delphi对象都是TObject的后代。因为Sender是TObject类型,所以任何对象都可以赋值给它。虽然您没有看见赋值的程序代码,但事实上发生事件的部件或控制部件已经赋给Sender了,这就是说Sender的值是响应发生事件的部件或控制部件的。

您可以使用保留字is来测试Sender以便找到调用这个事件处理过程的部件或控制部件的类型。Delphi中的一个显示drag-and-drop的DRAGDROP.DPR工程。加载它,可以查阅到DROPFONT.PAS库单元的代码,在Memo1DragOver方法中检查了一个对象变量的类型。在这种情形下,参数是Source而不是Sender。

procrdure TForm1.Memo1DragOver(SenderSource:TObject;X,Y:integer;

State:TDragState;var Accept:Boolean);

begin

Accept :=Source is TLabel;

end;

Source参数也是TObject类型,Source被赋值为那个被拖曳的对象。用Memo1DragOver方法的目的是确保只有标签可以被拖曳。Accept是布尔型参数,如果Accept为True,那么用户选择的部件可以被拖曳;反之当Accept的值为False时,用户就不可以拖曳选择控制部件。is保留字检查Source是否TLabel的类型,所以Accept只有在用户拖曳一个标签时才为真,并作为变参输出到函数之外。

下面的drag-and-drop展示的Memo1DragDrop事件处理过程中也使用了Source参数。这个方法是为了把Memo部件的字型改变成和放入这个备注控制部件的标签一样的字型:

procedure TForm1.Memo1DragDrop(SenderSource:TObject;

X,Y:Integer);

begin

Memo1.Font := (Source as TLabel).Font;

end;

当您在这个事件处理过程中编写赋值语句时,开发人员并不知道用户会放入哪一个标签,只有通过参考这个标签的名称(Source as TLabel)用户才能知道,并把标签类型赋给Memo1.TFont。Source包含了用户拖放控制部件的名称,只有当Source是一个标签时,这个事件处理过程才允许这个赋值发生。

2.2.7 建立非可视化对象

您在Delphi中使用的大部分对象都是您在设计和运行期间可以看见的部件,例如编辑框、按钮等;一些部件,如通用对话框(Common dialog box)等,在设计时看不见,而在运行时可以看见;另外有些部件,例如计时器(Timer)、数据源(Data Source)部件等,在程序的运行期间没有任何可视化的显示,但您却可以在您的应用程序中使用它们。

2.2.7.1说明一个非可视化对象

下面,通过一个简单的例子讲述如何建立自己的非可视化对象:

您可以用如下的方法,建立一个自己的TEmployee非可视化对象:

type

Temployee = class(TObject);

Name := String[25];

Title := String[25];

HourlyPayRate : Double;

function CalculatePayAmount:Double;

end;

在这种情况下,TEmployee从TObject继承下来,且包含三个域和一个方法。把您建立的类型说明放在库单元中的说明部分,并和窗体说明放在一起。在这个程序库单元的变量说明部分,说明一个新类型的变量:

var

Employee : TEmployee;

2.2.7.2用Create方法建立对象实例

TEmployee只是一个对象类型。除非通过一个构造函数的调用从而被实例取代或创建,否则一个对象并不存储在内存中。构造函数是一个方法,它为新对象配置内存并且指向这个新的对象。这个新的对象也被称为这个对象类型的一个实例。

建立一个对象的实例,需要调用Create方法,然后构造函数把这个实例赋给一个变量。如果您想说明一个TEmployee类型的实例,在您访问这个对象的任何域之前,您的程序代码必须调用Create。

Employee := TEmployee.Create;

Create方法并没有在TEmployee类型中说明,它继承自TObject类型。因为TEmployee是TObject的子类,所以它可以调用Create方法而创建一个TEmployee实例。然后把它赋给Employee变量。在创建了一个这样的对象后,您就可以象使用其他的Delphi对象一样访问Employee对象了。

2.2.7.3 撤销对象

当您使用完对象后,您应该及时撤销它,以便把这个对象占用的内存释放出来。您可以通过调用一个注销方法来撤销您的对象,它会释放分配给这个对象的内存。

Delphi的注销方法有两个:Destroy和Free。Delphi建议使用Free,因为它比Destroy更为安全,同时调用Free会生成效率更高的代码。

您可以用下列的语句释放用完的Employee对象:

Employee.Free;

和Create方法一样,Free方法也是TEmployee从TObject中继承过来的。把您的注销放在try…finally程序模块的finally部分,而把对象的程序代码放在try部分是编程的好习惯。这样,即使您的程序代码在使用对象时发生了异常事件,也会确保您为这个对象分配的内存会被释放。关于异常处理和try…finally程序模块的信息以及建立非可视化对象的例子,在后文中还将仔细讲述。