19.Delphi自定义部件的开发


======================================================
注:本文源代码点此下载
======================================================

delphi除了支持使用可视化部件所见即所得地建立应用程序外,还支持为开发应用而设计自己的部件。

在本章中将阐述如何为delphi应用程序编写部件。这一章将达到两个目的:

● 教你如何自定义部件

● 使你的部件成为delphi环境的有机组合部分

19.1 delphi部件原理

19.1.1 什么是部件

部件是delphi应用程序的程序构件。尽管大多数部件代表用户界面的可见元素,但部件也可以是程序中的不可见元素,如数据库部件。为弄清什么是部件可以从三个方面来考察它:功能定义、技术定义和经验定义。

1. 部件的功能定义

从最终用户角度,部件是在component palette上选择的,并在窗体设计窗口和代码窗口中操作的元素。从部件编写者角度,部件是代码中的对象。在编写部件之前,你应用相当熟悉已有的delphi部件,这样才能使你的部件适合用户的需要。编写部件的目标之一是使部件尽可能的类似其它部件。

2. 部件的技术定义

从最简单的角度看,部件是任何从tcomponent继承的对象。tcomponent定义了所有部件必须要的、最基本的行为。例如,出现在component palette上和在窗体设计窗口中编辑的功能。但是tcomponent并不知如何处理你的部件的具体功能,因此,你必须自己描述它。

3. 部件编写者自己的定义。

在实际编程中,部件是能插入delphi开发环境的任何元素。它可能具有程序的各种复杂性。简而言之,只要能融入部件框架,部件就是你用代码编写的一切。部件定义只是接口描述,本章将详细阐述部件框架,说明部件的有限性,正如说明编程的有限性。本章不准备教你用所给语言编写每一种部件,只能告诉编定代码的方法和怎样使部件融入delphi环境。

19.1.2 编写部件的不同之处

在delphi环境中建立部件和在应用程序中使用部件有三个重要差别:

● 编写部件的过程是非可视化的

● 编写部件需要更深入的关于对象的知识

● 编写部件需要遵循更多的规则

1. 编写部件是非可视化的

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

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

2. 编写部件需要更深的有关对象的知识

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

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

3. 编写部件要遵循更多的规则

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

19.1.3 建立部件过程概略

简而言之,建立自定义部件的过程包含下列几步:

● 建立包含新部件的库单元

● 从已有部件类型中继承得到新的部件类型

● 增加属性、方法和事件

● 用delphi注册部件

● 为部件的属性方法和事件建立help文件

如果完成这些工作,完整的部件包含下列4个文件

● 编译的库单元( .dcu文件)

● 选择板位图(.dcr文件)

● help文件(.hlp文件)

● help-keyword文件 (.kwf文件)

19.2 delphi部件编程方法

19.2.1 delphi部件编程概述

19.2.1.1 delphi可视部件类库

delphi的部件都是可视部件类库(vcl)的对象继承树的一部分,下面列出组成vcl的对象的关系。tcomponent是vcl中每一个部件的共同祖先。tcomponent提供了delphi部件正常工作的最基本的属性和事件。库中的各条分支提供了其它的更专一的功能。

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

19.2.1.2 建立部件的起点

部件是你在设计时想操作的任意程序元素。建立新部件意味着从已有类型中继承得到新的部件对象类。

建立新部件的主要途径如下:

● 修改已有的控制

● 建立原始控制

● 建立图形控制

● 建立windows控制的子类

● 建立非可视部件

下表列出了不同建立途径的起始类

表19.1 定义部件的起始点

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

途 径起 始 类

─────────────────────────────

修改已有部件任何已有部件,如tbutton、tlistbox

或抽象部件对象如tcustomlistbox

建立原始控制tcustomcontrol

建立图形控制 tgraphiccontrol

建立窗口控制的子类 twincontrol

建立非可视部件tcomponent

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

也可以继承非部件的其它对象,但无法在窗体设计窗口中操作它们。delphi包括许多这种对象,如tinifile、tfont等。

1. 修改已有控制

建立部件的最简单的方法是继承一个已有的、可用的部件并定制它。可以从delphi提供的任何部件中继承。例如,可以改变标准控制的缺省属性值,如tbutton。

有些控制,如listbox和grid等有许多相同变量,在这种情况下,delphi提供了抽象控制类型,从该类型出发可定制出许多的类型。例如,你也许想建立tlistbox的特殊类型,这种部件没有标准tlistbox的某些属性,你不能将属性从一个祖先类型中移去,因此你需要从比tlistbox更高层次的部件继承。例如tcustomlistbox,该部件实现了tcustomlistbox的所有属性但没有公布(publishing)它们。当从一个诸如tcustomlistbox的抽象类中继承时,你公布那些你想使之可获得的属性而让其它的保护起来(protected)。

2. 建立原始控制

标准控制是在运行时可见的。这些标准控制都从twincontrol,继承来的,当你建立原始控制时,你使用tcustomcontrol作为起始点。标准控制的关键特征是它具有窗口句柄,句柄保存在属性handle中,这种控制:

● 能接受输入焦点

● 能将句柄传送给windows api函数

如果控制不需要接受输入焦点,你可把它做成图形控制,这可能节省系统资源。

3. 建立图形控制

图形控制非常类似定制的控制,但它们没有窗口句柄,因此不占有系统资源。对图形控制最大的限制是它们不能接收输入焦点。你需要从tgraphiccontrol继承,它提供了作图的canvas和能处理wm_paint消息,你需要覆盖paint方法。

4. 继承窗口控制

windows中有一种称之为窗口类的概念,类似于面向对象的对象和类的概念。窗口类是windows中相同窗口或控制的不同实例之间共享的信息集合。当你用传统的windows编程方法创建一种新的控制,你要定义一个新的窗口类,并在windows中注册。你也能基于已有的窗口类创建新的窗口类。这就称为从窗口类继承。在传统的windows编程中,如果你想建立客户化的控制,你就必须将其做在动态链接库里,就象标准windows控制,并且提供一个访问界面。使用delphi,你能创建一个部件包装在已有窗口类之上。如果你已有客户化控制的库,并想使其运行在你的delphi应用中,那你就能创建一个使你能使用已有控制和获得新的控制的部件。在库单元stdctrls中有许多这样的例子。

5. 建立非可视化的部件

抽象对象类型tcomponent是所有部件的基础类型。从tcomponent直接继承所创建的部件就是非可视化部件。你编写的大多数部件都是可视控制。tcomponent定义了部件在formdesigner中所需的基本的属性和方法。因此,从tcomponent继承来的任何部件都具备设计能力。

非可视部件相当少,主要用它们作为非可视程序单元(如数据库单元)和对话框的界面。

19.2.1.3 建立新部件的方法

建立新部件的方法有两种:

● 手工建立部件

● 使用component expert

一旦完成建立后,就得到所需的最小功能单位的部件,并可以安装在component palette上。安装完后,你就能将新部件放置在窗体窗口,并可在设计阶段和运行阶段进行测试。你还能为部件增加新的特征、更新选择板、重新测试。

1. 手工创建部件

显然创建部件最容易的方法是使用component expert。然而,你也能通过手工来完成相同步骤。

手工创建部件需要下列三步:

● 创建新的库单元

● 继承一个部件对象

● 注册部件

⑴ 创建新的库单元

库单元是object pascal代码的独立编译单位。每一个窗体有自己的库单元。大多数部件(在逻辑上是一组)也有自己的库单元。

当你建立部件时,你可以为部件创建一个库单元,也可将新的部件加在已有的库单元中。

① 为部件创建库单元,可选择file/new... ,在new items对话框中选择unit,delphi将创建一个新文件,并在代码编辑器中打开它

② 在已有库单元中增加部件,只须选择file/open为已有库单元选择源代码。在该库单元中只能包含部件代码,如果该库单元中有一个窗体,将产生错误

⑵ 继承一个部件对象

每个部件都是tcomponent的后代对象。也可从tcontrol、tgraphiccontrol等继承。

为继承一个部件对象,要将对象类型声明加在库单元的interface部分。

例如,建立一个最简单的从tcomponent直接继承非可视的部件,将下列的类型定义加在部件单元的interface部分。

type

tnewcomponent=class(tcomponent)

……

end;

现在你能注册tnewcomponent。但是新部件与tcomponent没什么不同,你只创建了自己部件的框架。

⑶ 注册部件

注册部件是为了告诉delphi什么部件被加入部件库和加入component palette的哪一页。

为了注册一个部件:

① 在部件单元的interface部分增加一个register过程。register不带任何参数,因此声明很简单:

procedure register;

如果你在已有部件的库单元中增加部件,因为已有register 过程,因此不须要修改声明。

② 在库单位的implementation部件编写register过程为每一个你想注册的部件调用过程registercomponents,过程registercomponents带两个参数:component palette的页名和部件类型集。例如,注册名为tnewcomponent的部件,并将其置于component palette的samples页,在程序中使用下列过程:

procedure register;

begin

registercomponents('samples', [tnewcomponent]);

end;

一旦注册完毕,delphi自动将部件图标显示在component palette上。

2. 使用component expert(部件专家)

你能使用component expert创建新部件。使用component expert简化了创建新部件最初阶段的工作,因为你只需描述三件事:

● 新部件的名字

● 祖先类型

● 新部件要加入的component palette页名

component expert执行了手工方式的相同工作:

● 建立新的库单元

● 继承得到新部件对象

● 注册部件

但component expert不能在已有单元中增加部件。

可选择file/new... ,在new items对话框中选择component,就打开component expert对话框。

填完component expert对话框的每一个域后,选择ok。delphi建立包括新部件和register过程的库单元,并自动增加uses语句。

你应该立刻保存库单元,并给予其有意义的名字。

19.2.1.4. 测试未安装的部件

在将新部件安装在component palette之前就能测试部件运行时的动作。这对于调试新部件特别有用,而且还能用同样的技术测试任意部件,无论该部件是否出现在component palette上。

从本质上说,你通过模仿用户将部件放置在窗体中的delphi的动作来测试一个未安装的部件。

可按下列步骤来测试未安装的部件

1. 在窗体单元的uses语句中加入部件所在单元的名字

2. 在窗体中增加一个对象域来表示部件

这是自己增加部件和delphi增加部件的方法的主要不同点。

你将对象域加在窗体类型声明底部的public部分。delphi则会将对象域加在底部声明的上面。

你不能将域加在delphi管理的窗体类型的声明的上部。在这一部分声明的对象域将相应在存储在dfm文件中。增加不在窗体中存在的部件名将产生dfm文件无效的错误。

3. 附上窗体的oncreate事件处理过程

4. 在窗体的oncreate处理过程中构造该部件

当调用部件的构造过程时,必须传递owner参数(由owner负责析构该部件)一般说来总是将self作为owner的传入参数。在oncreate中,self是指窗体。

5. 给component的parent属性赋值

设置parent属性往往是构造部件后要做的第一件事时。parent在形式上包含部件,一般来说parent是窗体或者goupbox、panel。通常给parent赋与self,即窗体。在设置部件的其它属性之前最好先给parent赋值。

6. 按需要给部件的其它属性赋值

假设你想测试名为tnewcomponent类型的新部件,库单元名为newtest。窗体库单元应该是这样的;

unit unitl;

interface

uses sysutils, windows, messages, classes, grophics, controls, forms, dialogs,

newtest;

type

tforml = class(tform)

procedure formcreate(sender: tobject);

private

{ private申 明 }

public

{ public申 明 }

newcomponent: tnewcomponent;

end;

var

forml: tforml;

implementation

{$r *.dfm }

procedure tforml.formcreate ( sender: tobject ) ;

begin

newcomponent := tnewcomponent.create ( self );

newcompanent.parent := self;

newcompanent.left := 12;

end;

end.

19.2.1.5 编写部件的面向对象技术

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

1. 建立部件

部件用户和部件编写者最基本的区别是用户处理对象的实例,而编写者创建新的对象类型。这个概念是面向对象程序设计的基础。例如,用户创建了一个包含两个按钮的窗体,一个标为ok,另一个标为cancel,每个都是tbutton的实例,通过给text、default和cancel等属性赋不同的值,给onclick事件赋予不同的处理过程,用户产生了两个不同的实例。

建立新部件一般有两个理由

● 改变类型的缺省情况,避免反复

● 为部件增加新的功能

目的都是为了建立可重用对象。如果从将来重用的角度预先计划和设计,能节省一大堆将来的工作。

在程序设计中,避免不必要的重复是很重要的。如果发现在代码中一遍又一遍重写相同的行,就应当考虑将代码放在子过程或函数中,或干脆建立一个函数库。

设计部件也是这个道理,如果总是改变相同的属性或相同的方法调用,那应创建新部件。

创建新部件的另一个原因是想给已有的部件增加新的功能。你可以从已有部件直接继承(如listbox)或从抽象对象类型继承(如tcomponent,tcontrol)。你虽然能为部件增加新功能,但不能将原有部件的属性移走,如果要这样做的话,就从该父对象的祖先对象继承。

2. 控制部件的访向

object pascal语言为对象的各部分提供了四个级别的访问控制。访问控制让你定义什么代码能访问对象的哪一部分。通过描述访问级别,定义了部件的接口。如果合理安排接口,将提高部件的可用性和重用性。

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

下表列出各保护级别:

表19.2 对象定义中的保护级别

━━━━━━━━━━━━━━━━━━━

保护级用处

───────────────────

private 隐藏实现细节

protected定义开发者接口

public 定义运行时接口

published 定义设计时接口

━━━━━━━━━━━━━━━━━━━

所有的保护级都在单元级起作用。如果对象的某一部分在库单元中的一处可访向,则在该库单元任意处都可访向。

⑴ 隐藏实现细节

如果对象的某部分被声明为private,将使其它库单元的代码无法访问该部分,但包含声明的库单元中的代码可以访问,就好象访问public一样,这是和c++不同的。

对象类型的private部分对于隐藏详细实现是很重要的。既然对象的用户不能访问,private部分,你就能改变对象的实现而不影响用户代码。

下面是一个演示防止用户访问private域的例子:

unit hideinfo;

interface

uses sysutils, wintypes, winprocs, messages, classes, graphics, controls, forms,

dialogs;

type

tsecretform = class(tform) { 声明新的窗体窗口 }

procedure formcreate(sender: tobject);

private { declare private part }

fsecretcode: integer; { 声明private域 }

end;

var

secretform: tsecretform;

implementation

procedure tsecretform.formcreate(sender: tobject);

begin

fsecretcode := 42;

end;

end.

unit testhide; { 这是主窗体库单元 }

interface

uses sysutils, wintypes, winprocs, messages, classes, graphics, controls, forms,

dialogs, hideinfo; { 使用带tsecretform声明的库单元 }

type

ttestform = class(tform)

procedure formcreate(sender: tobject);

end;

var

testform: ttestform;

implementation

procedure ttestform.formcreate(sender: tobject);

begin

secretform.fsecretcode := 13; {编译过程将以"field identifier expected"错误停止}

end;

end.

⑵ 定义开发者接口

将对象某部分声明为protected,可使在包含该部件声明的库单元之外的代码无法访问,就象private部分。protected部分的不同之处是,某对象继承该对象,则包含新对象的库单元可以访问protected部分,你能使用protected声明定义开发者的接口。也就是说。对象的用户不能访向protected部分,但开发者通过继承就可能做到,这意味着你能通过protected部分的可访问性使部件编写者改变对象工作方式,而又不使用户见到这些细节。

⑶ 定义运行时接口

将对象的某一部分定义为public可使任何代码访问该部分。如果你没有对域方法或属性加以private、protected、public的访问控制描述。那么该部分就是published。

因为对象的public部分可在运行时为任何代码访问,因此对象的public部分被称为运行接口。运行时接口对那些在设计时没有意义的项目,如依靠运行时信息的和只读的属性,是很有用的。那些设计用来供用户调用的方法也应放在运行时接口中。

下例是一个显示两个定义在运行时接口的只读属性的例子:

type

tsamplecomponent = class(tcomponent)

private

ftempcelsius: integer; { 具体实现是private }

function gettempfahrenheit: integer;

public

property tempcelsius: integer read ftempcelsius; { 属性是public }

property tempfahrenheit: integer read gettempfahrenheit;

end;

function gettempfahrenheit: integer;

begin

result := ftempcelsius * 9 div 5 + 32;

end;

既然用户在设计时不能改变public部分的属性的值,那么该类属性就不能出现在object inspector窗口中。

⑷ 定义设计时接口

将对象的某部分声明为published,该部分也即为public且产生运行时类型信息。但只有published部分定义的属性可显示在object inspector窗口中。对象的published部分定义了对象的设计时接口。设计时接口包含了用户想在设计时定制的一切特征。

下面是一个published属性的例子,因为它是published,因此可以出现在object inspector窗口:

tsamplecomponent = class(tcomponent)

private

ftemperature: integer; { 具体实现是 private }

published

property temperature: integer read ftemperature write ftemperature; { 可写的 }

end;

3. 派送方法

派送(dispatch)这个概念是用来描述当调用方法时,你的应用程序怎样决定执行什么样的代码,当你编写调用对象的代码时,看上去与任何其它过程或函数调用没什么不同,但对象有三种不同的派送方法的方式。

这三种派送方法的类型是:

● 静态的

● 虚拟的

● 动态的

虚方法和动态方法的工作方式相同,但实现不同。两者都与静态方法相当不同。理解各种不同的派送方法对创建部件是很有用的。

⑴ 静态方法:

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

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

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

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

在下列代码中,第一个部件声明了两静态方法,第二个部件,声明了相同名字的方法取代第一个部件的方法。

type

tfirstcomponent = class(tcomponent)

procedure move;

procedure flash;

end;

tsecondcomponent = class(tfirstcomponent)

procedure move; { 尽管有相同的声明,但与继承的方法不同 }

function flash(howoften: integer): integer; { 同move方法一样 }

end;

⑵ 虚方法

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

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

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

覆盖一个方法是扩展它,而不是取代它。后代对象可以重定义和重实现在祖先对象中声明的任何方法。但无法覆盖一个静态方法。覆盖一个方法,要在方法声明的结尾增加override指令,在下列情况,使用override将产生编译错误:

● 祖先对象中不存在该方法

● 祖先对象中相同方法是静态的

● 声明与祖先对象的(如名字、参数)不匹配

下列代码演示两个简单的部件。第一个部件声明了三个方法,每一个使用不同的派送方式,第二个部件继承第一个部件,取代了静态方法,覆盖了虚拟方法和动态方法。

type

tfirstcomponent = class(tcustomcontrol)

procedure move; { 静态方法 }

procedure flash; virtual; { 虚 方 法 }

procedure beep; dynamic; { 动态虚拟方法 }

end;

tsecondcomponent = class(tfirstcomponent)

procedure move; { 声明了新的方法 }

procedure flash; override; { 覆盖继承的方法 }

procedure beep; override; { 覆盖继承的方法 }

end;

⑶ 动态方法

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

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

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

⑷ 对象与指针

在object pascal中,对象实际上是指针。编译器自动地为程序创建对象指针,因此在大多数情况下,你不需要考虑对象是指针。但当你将对象作为参数传递时,这就很重要了。通常,传递对象是按值而非按引用,也就是说,将对象声明为过程的参数时,你不能用var参数,理由是对象已经是指针引用了。

19.2.2 delphi部件编程

19.2.2.1 创建属性

属性(property)是部件中最特殊的部分,主要因为部件用户在设计时可以看见和操作它们,并且在交互过程中能立即得到返回结果。属性也很重要,因为如果将它们设计好后,将使用户更容易地使用,自己维护起来也很容易。

为了使你在部件中更好地使用属性,本部分将介绍下列内容:

● 为什么要创建属性

● 属性的种类

● 公布(publishing)继承的属性

● 定义部件属性

● 编写属性编辑器

1. 为什么要创建属性

属性提供非常重要的好处,最明显的好处是属性在设计时能出现在object inspector窗口中,这将简化编程工作,因为你只需读用户所赋的值,而不要处理构造对象的参数。

从部件使用者的观点看,属性象变量。用户可以给属性赋值或读值,就好象属性是对象的域。

从部件编写者的观点看属性比对象的域有更强的功能;

⑴ 用户可以在设计时设置属性

这是非常重要的,因为不象方法,只能在运行时访问。属性使用户在运行程序之前就能定制部件,通常你的部件不应包含很多的方法,它们的功能可以通过属性来实现。

⑵ 属性能隐藏详细的实现细节

⑶ 属性能引起简单地赋值之外的响应,如触发事件

⑷ 用于属性的实现方法可以是虚拟方法,这样看似简单的属性在不同的部件中,将实现不同的功能。

2. 属性的类型

属性可以是函数能返回的任何类型,因为属性的实现可以使用函数。所有的pascal类型,兼容性规则都适用属性。为属性选择类型的最重要的方面是不同的类型出现在object inspector窗口中的方式不同。object inspector将按不同的类型决定其出现的方式。

你也能在注册部件时描述不同的属性编辑器。

下表列出属性出现在object inspector窗口中的方式

表19.3 属性出现在object inspector窗口中的方式

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

属性类型处 理 方 式

───────────────────────────────────────

简单类型numeric、character和 string属性出现在object inspector中,用户可

以直接编辑

枚举类型 枚举类型的属性显示值的方式定义在代码中。选择时将出现下拉

式列表框,显示所有的可能取值。

集合类型 集合类型出现在object inspector窗口中时正如一个集合,展开后,用

户通过将集合元素设为true或false来选择。

对象类型 作为对象的属性本身有属性编辑器,如果对象有自己的published属

性,用户在object inspector中通过展开对象属性列,可以独立编辑它们,

对象类型的属性必须从tpersistent继承。

数组类型 数组属性必须有它们自己的属性编辑器,object inspector没有内嵌对数

组属性编辑的支持。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. 公布继承的属性

所有部件都从祖先类型继承属性。当你从已有部件继承时,新部件将继承祖先类型的所有属性。如果你继承的是抽象类,则继承的属性是protected或public,但不是published。如想使用户访问protected或public属性,可以将该属性重定义为published。如果你使用twincontrol继承,它继承了ctl3d属性,但是protected的,因此用户在设计和运行时不能访问ctl3d,通过在新部件中将ctl3d重声明为published,就改变了ctl3d的访问级别。下面的代码演示如何将ctl3d声明为published,使之在设计时可被访问。

type

tsamplecomponent=class(twincontrol)

published

property ctl3d;

end;

4. 定义部件属性

⑴ 属性的声明

声明部件的属性,你要描述:

● 属性名

● 属性的类型

● 读和设置属性值的方法

至少,部件属性应当定义在部件对象声明的public部分,这样可以在运行时很方便地从外部访问;为了能在设计时编辑属性,应当将属性在published部分声明,这样属性能自动显示在object inspector窗口中。下面是典型的属性声明:

type

tyourcomponent=class(tcomponent)

private

fcount: integer { 内部存储域 }

function getcount: integer; { 读方法 }

procedure setcount(acount: integer); { 写方法 }

pubilic

property count: integer read getcount write setcount;

end;

⑵ 内部数据存储

关于如何存储属性的数据值,delphi没有特别的规定,通常delphi部件遵循下列规定:

● 属性数据存储在对象的数据域处

● 属性对象域的标识符以f开头,例如定义在tcontrol中的属性fwidth

● 属性数据的对象域应声明在private部分

后代部件只应使用继承的属性自身,而不能直接访问内部的数据存储。

⑶ 直接访问

使属性数据可用的最简单的办法是直接访问。属性声明的read 和write部分描述了怎样不通过调用访问方法来给内部数据域赋值。但一般都用read进行直接访问,而用write进行方法访问,以改变部件的状态。

下面的部件声明演示了怎样在属性定义的read 和write部分都采用直接访问:

type

tyourcomponent=class(tcomponent)

private { 内部存储是私有 }

freadonly: boolean; { 声明保存属性值的域 }

published { 使属性在设计时可用 }

property readonly: boolean read freadonly write freadonly;

end;

⑷ 访问方法

属性的声明语法允许属性声明的read和write部分用访问方法取代对象私有数据域。不管属性是如何实现它的read 和write部分,方法实现应当是private,后代部件只能使用继承的属性访问。

① 读方法

属性的读方法是不带参数的函数,并且返回同属性相同类型的值。通常读函数的名字是“get”后加属性名,例如,属性count的读方法是getcount。不带参数的唯一例外是数组属性。如果你不定义read方法,则属性是只写的。

② 写方法

属性的写方法总是只带一个参数的过程。参数可以是引用或值。通常过程名是"set"加属性名。例如,属性count的写方法名是setcount。参数的值采用设置属性的新值,因此,写方法需要执行在内部存储数据中写的操作。

如果没有声明写方法,那么属性是只读的。

通常在设置新值前要检测新值是否与当前值不同。

下面是一个简单的整数属性count的写方法:

procedure tmycomponent.setcount( value: integer);

begin

if valuemaxvalue ) then

raise epropertyerror.create (fmtloadstr(soutofrange,

[minvalue,maxvalue]));

setordvalue (l);

end;

⑶ 将属性作为一个整体来编辑

delphi支持提供用户以对话框的方式可视化地编辑属性。这种情况常用于对对象类型属性的编辑。一个典型的例子是font属性,用户可以找开font对话框来选择字体的属性。

提供整体属性编辑对话框,要覆盖属性编辑对象的edit方法。edit方法也使用"get"和"set"方法。

在大多数部件中使用的color属性将标准的windows颜色对话框作为属性编辑器。下面是tcolorproperty的edit方法

procedure tcolorproperty.edit

var

colordialog: tcolordialog;

begin

colordialog := tcolordialog.create(application); { 创建编辑器 }

try

colordialog.color := getordvalue; { 使用已有的值 }

if colordialog.execute then

setordvalue (colordialog.color);

finally

colordialog.free;

end;

end;

⑷ 描述编辑器的属性

属性编辑必须告诉object inspector窗口如何采用合适的显示工具。例如object inspector窗口需要知道属性是否有子属性,或者是否能显示可能取值的列表。描述编辑器的属性通常覆盖属性编辑器的getattributes方法。

getattributes返回tpropertyattributes类型的集合。集合中包括表中任何或所有的值:

表19.6 属性编辑器特征标志

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标志 含 义 相关方法

──────────────────────────────

pavaluelist 编辑器能给予一组枚举值 getvalues

pasubpropertie 属性有子属性 getpropertises

padialog 编辑器能显示编辑对话框 edit

pamultiselect 当用户选择多于一个部件

时,属性应能显示 n/a

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

color属性是灵活的,它允许在object inspector窗口中以多种方式选择他们。或者键入,或者从列表中选择定编辑器。因此tcolorproperty的getattributes方法在返回值中包含多种属性。

function tcolorproperty.getattributes: tprorertyattributes;

begin

result := [pamultiselect, padialog, pavaluelist];

end;

⑸ 注册属性编辑器

一旦创建属性编辑器,必须在delphi中注册。注册属性编辑器时,要与某种属性相联。

调用registerpropertyeditor过程来注册属性编辑器。该过程接受四个参数:

● 要编辑的属性的类型信息的指针。这总是通过调用调用typeinfo函数得到,如typeinfo ( tmycomponent )

● 编辑器应用的部件类型,如果该参数为nil则编辑器应用于所给的类型的所有属性

● 属性名,该参数只有在前一参数描述了部件的情况下才可用

● 使用该属性编辑器的属性的类型

下面引用了注册标准部件的过程:

procedure register;

begin

registerpropertyeditor (typeinfo(tcomponent), nil, tcomponentproperty,

registerpropertyeditor(typeinfo(tcomponentname), tcomponent,

'name', (componentnamepropety);

registerpropertyeditor (typeinfo(tmenuitem), tmenu, '', tmenuitemproperty);

end;

这三句表达式使用registerpropertyeditor三种不同的用法:

● 第一种最典型

它注册了用于所有tcomponent类型属性的属性编辑器tcomponentproperty。通常,当为某种类型属性注册属性编辑器时,它就能应用于所有这种类型的属性,因此,第二和第三个参数为nil。

● 第二个表达式注册特定类型的属性编辑器

它为特定部件的特定属性注册属性编辑器,在这种情况下,编辑器用于所有部件的name属性。

● 第三个表达式介于第一个和第二个表达式之间

它为部件tmenu的tmenuitem类型的所有属性注册了属性编辑器。

19.2.2.2 创建事件

事件是部件的很重要的部分。事件是部件必须响应的系统事件与响应事件的一段代码的联接。响应代码被称为事件处理过程,它总是由部件用户来编写。通过使用事件,应用开发者不需要改变部件本身就能定制部件的行为。作为部件编写者,运用事件能使应用发者定制所有的标准delphi部件。要创建事件,应当理解:

● 什么是事件

● 怎样实现标准事件

● 怎样定义自己的事件

1. 什么是事件

事件是联接发生的事情与某些代码的机制,或者说是方法指针,一个指向特定对象实例的特定方法的指针。从部件用户的角度,事件是与系统事件(如onclick)有关的名称,用户能给该事件赋特定的方法供调用。例如,按钮buttonl有onclick方法,缺省情况下delphi在包含该按钮的窗体中产生一个为buttonlclick的方法,并将其赋给onclick。当一个click事件发生在按钮上时,按钮调用赋给onclick的方法buttonlclick:

部件用户将事件看作是由用户编写的代码,而事件发生时由系统调用的处理办法。

从部件编写者角度事件有更多的含义。最重要的是提供了一个让用户编写代码响应特定事情的场所。

要编写一个事件,应当理解:

● 事件和方法指针

● 事件是属性

● 事件处理过程类型

● 事件处理过程是可选的

⑴ 事件是方法指针

delphi使用方法指针实现事件。一个方法指针是指向特定对象实例的特定方法的特定指针。作为部件编写者,能将方法指针作为一种容器。你的代码一发现事情发生,就调用由用户定义的方法。

方法指针的工作方式就象其它的过程类型,但它们保持一个隐含的指向对象实例的指针。所有的控制都继承了一个名为click的方法,以处理click事件。click方法调用用户的click事件处理过程。

procedure tcontrol.click;

begin

if assigned(onclick ) then onclick( self );

end;

如果用户给control的onclick事件赋了处理过程(handle),那鼠标点按control时将导致方法被调用。

⑵ 事件是属性

部件采用属性的形式实现事件。不象大多数其它属性,事件不使用方法来使实现read和write部分。事件属性使用了相同类型的私有对象域作为属性。按约定域名在属性名前加“f”。例如onclick方法的指针,存在tnotifyevent类型fonclick域中。onclick事件属性的声明如下:

type

tcontrol=class ( tcomponent )

private

fonclick: tnofifyevent; { 声明保存方法指针的域 }

protected

property onclick: tnotifyevent read fonclick write fonclick;

end;

象其它类型的属性一样,你能在运行时设置和改变事件的值。将事件做成属性的主要好处是部件用户能在设计时使用object inspector设置事件处理过程。

⑶ 事件处理过程类型

因为一个事件是指向事件处理过程的指针,因此事件属性必须是方法指针类型,被用作事件处理过程的代码,必须是相应的对象的方法。

所有的事件方法都是过程。为了与所给类型的事件兼容,一个事件处理过程必须有相同数目和相同类型的相同顺序的参数。delphi定义了所有标准事件处理过程的方法类型,当你创建自己的事件时,你能使用已有的事件类型,或创建新的。虽然不能用函数做事件处理过程,但可以用var参数得到返回信息。

在事件处理过程中传递var参数的典型例子是tkeypressevent类型的keypressed事件。tkeypressevent定义中含有两个参数。一个指示哪个对象产生该事件。另一个指示那个键按下:

type

tkeypressevent=procedure( sender: tobject; var key: char) of object;

通常key参数包含用户按下键的字符。在某些情况下,部件的用户可能想改变字符值。例如在编辑器中强制所有字符为大写,在这种情况下,用户能定义下列的事件处理过程:

procedure tforml.editlkeypressed( sender: tobject; var key: char);

begin

key := upcase( key );

end;

也可使用var参数让用户覆盖缺省的处理。

⑷ 事件处理过程是可选的

在为部件创建事件时要记住部件用户可能并不编写该事件的处理过程。这意味着你的部件不能因为部件用户没有编写处理代码而出错。这种事件处理过程的可选性有两个方面:

① 部件用户并非不得不处理事件

事件总是不断地发生在windows应用程序中。例如,在部件上方移动鼠标就引起windows发送大量的mouse-move消息给部件,部件将鼠标消息传给onmousemove事件。在大多数情况下,部件用户不需要关心mousemove事件,这不会产生问题,因为部件不依赖鼠标事件的处理过程。同样,自定义部件也不能依赖用户的事件处理过程。

② 部件用户能在事件处理过程写任意的代码

一般说来,对用户在事件处理过程中的代码没有限制。delphi部件库的部件都支持这种方式以使所写代码产生错误的可能性最小。显然,不能防止用户代码出现逻辑错误。

2. 怎样实现标准事件

delphi带的所有控制继承了大多数windows事件,这些就是标准事件。尽管所有这些事件都嵌在标准控制中,但它们缺省是protected,这意味着用户无法访问它们,当创建控制时,则可选择这些事件使用户可用。将这些标准事件嵌入自定义控制需要考虑如下:

● 什么是标准事件

● 怎样使事件可见

● 怎样修改标准事件处理过程

⑴ 什么是标准事件

有两种标准事件:用于所有控制和只用于标准windows控制。

最基本的事件都定义在对象tcontrol中。窗口控制、图形控制和自定义控制都继承了这些事件,下面列出用于所有控制的事件:

onclick ondragdrop onenddrag onmousemove

ondblclick ondragover onmousedown onmouseup

所有标准事件在tcontrol中都定义了相应的protected动态方法,只是没有加“on”例如onclick事件调用名为click的方法。

标准控制(从twincontrol继承)具有下列事件:

onenter onkeydown onkeypress onkeyup onexit

正如tcontrol中的标准事件,窗口控制也有相应protected动态方法。

⑵ 怎样使事件可见

标准事件的声明是protected,如果想使用户在运行时或设计时能访问它们,就需要将它们重声明为public和 published。重声明属性而不描述它的实现将继承相同的实现方法,只是改变了访问级别。例如,创建一个部件并使它的onclick事件出现在运行时,你可增加下面的部件声明:

type

tmycontrol=class(tcustomcontrol)

published

property onclick; { 使onclick在objectinspector中可见 }

end;

⑶ 怎样修改标准事件处理过程

如果想修改自定义部件响应某种事件的方法,可以重写代码并将其赋给事件。将联接每个标准事件的方法声明的protected是出于慎密的考虑。通过,覆盖实现方法,能修改内部事件处理过程,通过调用继承的方法,能保持标准事件处理过程。

调用继承的方法的顺序是很重要的。一般首先调用继承的方法,允许用户的事件处理过程代码在你的定制代码前执行。然而也有在调用继承的方法之前执行自己的代码情况出现。

下面是一个覆盖click事件的例子:

procedure tmycontrol.click;

begin

inherited click; { 执行标准处理,包括调用事件处理过程你自己的定制代码 }

end;

3. 定义自己的事件

定义全新的事件的情况是很少见的。只有当部件的行为完全不同于任何其它事件才需要定义新事件。定义新事件一般包含三个步骤:

● 触发事件

● 定义处理过程类型

● 声明事件

● 调用事件

⑴ 触发事件

定义自己的事件要遇到的第一个关键是:当使用标准事件时你不需要考虑由什么触发事件。对某些事件,问题是显然的。例如:一个mousedown事件是在用户按下鼠标的左键时发生,windows给应用发送wm_lbuttondown消息。接到消息后,一个部件调用它的mousedown方法,它依次调用用户的onmousedown事件处理过程代码。但是有些事件却不是那么可以描述清楚的。例如:滚行杠有一个onchange事件,可被各种情况触发,包括按键、鼠标点按或其它按制中的改变。当定义事件时,你必须使各种情况的发生调用正确的事件。

这里有tcontrol处理wm_lbuttondown消息的方法,domousedown是私有的实现方法,它提供了一般的处理左、右和中按钮的方法,并将windows消息的参数转换为mousedown方法的值。

type

tcontrol = class(tcomponent)

private

fonmousedown: tmouseevent;

procedure domousedown(var message: twmmouse; button: tmousebutton;

shift: tshiftstate);

procedure wmlbuttondown(var message: twmlbuttondown);

message m_lbuttondown;

protected

procedure mousedown(button: tmousebutton; shift: tshiftstate;

x, y: integer); dynamic;

end;

procedure tcontrol.mousedown(button: tmousebutton; shift: tshiftstate; x, y: integer);

begin

if assigned(fonmousedown) then

fonmousedown(self, button, shift, x, y); { 调用事件处理过程 }

end;

procedure tcontrol.domousedown(var message: twmmouse; button: tmousebutton;

shift: shiftstate);

begin

with message do

mousedown(button, keystoshiftstate(keys) + shift, xpos, ypos); { 调用动态方法 }

end;

procedure tcontrol.wmlbuttondown(var message: twmlbuttondown);

begin

inherited; { perform default handling }

if cscapturemouse in controlstyle then

mousecapture := true;

if csclickevents in controlstyle then

include(fcontrolstate, csclicked);

domousedown(message, mbleft, []); { 调用常规的mouse-down 方法 }

end;

当两种事情-状态变化和用户交互—发生时,处理机制是相同的,但过程稍微不同。用户交互事件将总是由windows消息触发。状态改变事件也与windows消息有关,但它们也可由属性变化或其它代码产生。你拥有对自定义事件触发的完全控制。

⑵ 定义处理过程类型

一旦你决定产生事件,就要定义事件如何被处理,这就是要决定事件处理过程的类型。在大多数情况下,定义的事件处理过程的类型是简单的通知类型(tnotifyevent)和已定义的事件类型。

通知事件只是告诉你特定的事件发生了,而没有描述什么时候和什么地方。通知事件使用时只带一个tobject类型的参数,该参数是sender。然而所有通知事件的处理过程都知道是什么样的事件发生和发生在那个部件。例如:click事件是通知类型。当编写click事件的处理过程时,你知道的是click事件发生和哪个部件被点按了。通知事件是单向过程。没有提供反馈机制。

在某些情况下,只知道什么事件发生和发生在那个部件是不够的。如果按键事件发生,事件处理过程往往要知道用户按了哪个键。在这种情况下,需要事件处理过程包含有关事件的必要信息的参数。如果事件产生是为了响应消息,那么传递给事件的参数最好是直接来自消息参数。

因为所有事件处理过程都是过程,所以从事件处理过程中返回信息的唯一方法是通过var参数。自定义部件可以用这些信息决定在用户事件处理过程执行后是否和怎样处理事件。

例如,所有的击键事件(onkeydown、onkeyup和onkeypressed)通过名为key的var参数传递键值。为了使应用程序看见包含在事件中的不同的键,事件处理过程可以改变key变量值。

⑶ 声明事件

一旦你决定了事件处理过程的类型,你就要准备声明事件的方法指针和属性。为了让用户易于理解事件的功能,应当给事件一个有意义的名字,而且还要与部件中相似的属性的名称保持一致。

delphi中所有标准事件的名称都以“on”开头。这只是出于方便,编译器并不强制它。object inspector是看属性类型来决定属性是否是事件,所有的方法指针属性都被看作事件,并出现在事件页中。

⑷ 调用事件

一般说来,最好将调用集中在事件上。就是说在部件中创建一个虚方法来调用用户的事件处理过程和提供任何缺省处理。当调用事件时,应考虑以下两点:

● 必须允许空事件

● 用户能覆盖缺省处理

不能允许使空事件处理过程产生错误的情况出现。就是说,自定义部件的正常功能不能依赖来自用户事件处理过程的响应。实际上,空事件处理过程应当产生与无事件处理过程一样的结果。

部件不应当要求用户以特殊方式使用它们。既然一个空事件处理过程应当与无事件处理过程一样动作,那么调用用户事件处理过程的代码应当象这样:

if assigned(onclick) then onclick(self);

{ 执行缺省处理 }

而不应该有这样的代码:

if assigned(onclick) then

onclick(self)

else

…; { 执行缺省处理 }

对于某些种类的事件,用户可能想取代缺省处理甚至删除所有的响应。为支持用户实现这种功能,你需要传递var参数给事件处理过程,并在事件处理过程返回时检测某个值。空事件处理过程与无事件处理过程有相同作用。因为空事件处理过程不会改变任何var参数值。所以缺省处理总是在调用空事件处理过程后发生。

例如在处理key-press事件,用户可以通过将var参数key的值设置为空字符(#0)来压制部件的缺省处理,代码如下:

if assigned(onkeypress) then onkeypress(self key);

if key = wm_movsefirst) and

(message.msg value then

begin

fshape := value;

invalidate(true); { 强制新形状的重画 }

end;

end;

2. 覆盖constructor和destructor

为了改变缺省属性值和初始化部件拥有的对象,需要覆盖继承的constructor和destructor方法。

图形控制的缺省大小是相同的,因此需要改变width和height属性。

本例中shape控制的大小的初始设置为边长65个象素点。

⑴ 在部件声明中增加覆盖constructor

type

tsampleshape=class(tgraphiccontrol)

public

constructor create(aowner: tcomponent); override;

end;

⑵ 用新的缺省值重新声明属性height和width

type

tsampleshape=class(tgrahiccontrol)

published

property height default 65;

property width default 65;

end;

⑶ 在库单元的实现部分编写新的constructor

constructor tsampleshape.create(aowner: tcomponent);

begin

inherited create(aowner);

width := 65;

height := 65;

end;

3. 公布pen和brush

在缺省情况下,一个canvas具有一个细的、黑笔和实心的白刷,为了使用户在使用shape控制时能改变canvas的这些性质,必须能在设计时提供这些对象;然后在画时使用这些对象,这样附属的pen或brush被称为owned对象。

管理owned对象需要下列三步:

● 声明对象域

● 声明访问属性

● 初始化owned对象

⑴ 声明owned对象域

拥有的每一个对象必须有对象域的声明,该域在部件存在时总指向owned对象。通常,部件在constructor中创建它,在destructor中撤消它。

owned对象的域总是定义为私有的,如果要使用户或其它部件访问该域,通常要提供访问属性。

下面的代码声明了pen和brush的对象域:

type

tsampleshape=class(tgraphiccontrol)

private

fpen: tpen;

fbrush: tbrush;

end;

⑵ 声明访问属性

可以通过声明与owned对象相同类型的属性来提供对owned对象的访问能力。这给使用部件的开发者提供在设计时或运行时访问对象的途径。

下面给shape控制提供了访问pen和brush的方法

type

tsampleshape=class(tgraphiccontrol)

private

procedure setbrush(value: tbrush);

procedure setpen(value: tpen);

published

property brush: tbrush read fbrush write setbrush;

property pen: tpen read fpen write setpen;

end;

然后在库单元的implementation部分写setbrush和setpen方法:

procedure tsampleshape.setbrush(value: tbrush);

begin

fbrush.assign(value);

end;

procedure tsampleshape.setpen(value: tpen);

begin

fpen.assign(value);

end;

⑶ 初始化owned对象

部件中增加了的新对象,必须在部件constructor中建立,这样用户才能在运行时与对象交互。相应地,部件的destructor必须在撤消自身之前撤消owned对象。

因为shape控制中加入了pen和brush对象,因此,要在constructor中初始化它们,在destructor中撤消它们。

① 在shape控制的constructor中创建pen和brush

constructor tsampleshape.create(aowner: tcomponent);

begin

inherited create(aowner);

width := 65;

height := 65;

fpen := tpen.create;

fbrush := tbrush.create;

end;

② 在部件对象的声明中覆盖destructor

type

tsampleshape=class(tgraphiccontrol)

public

construstor.create(aowner: tcomponent); override;

destructor.destroy; override;

end;

③ 在库单元中的实现部分编写新的destructor

destructor tsampleshape.destroy;

begin

fpen.free;

fbrush.free;

inherited destroy;

end;

④ 设置owned对象的属性

处理pen和brush对象的最后一步是处理pen和brush发生改变时对shape控制的重画问题。pen和brush对象都有onchange事件,因此能够在shape控制中声明onchange事件指向的事件处理过程。

下面给shape控制增加了该方法并更新了部件的constructor以使pen和brush事件指向新方法:

type

tsampleshape = class(tgraphiccontrol)

published

procdeure stylechanged(sender: tobject);

end;

implemintation

constructor tsampleshape.create(aowner:tcomponent);

begin

inherited create(aowner);

width := 65;

height := 65;

fpen := tpen.create;

fpen.onchange := stylechanged;

fbrush := tbrush.create;

fbrush.onchange := stylechanged;

end;

procedure tsampleshape.stylechanged(sender: tobject);

begin

invalidate(true);

end;

当变化发生时,部件重画以响应pen或brush的改变。

4. 怎样画部件图形

图形控制基本要素是在屏幕上画图形的方法。抽象类tgraphiccontrol定义了名为paint的虚方法,可以覆盖该方法来画所要的图形。

shape控制的paint方法需要做:

● 使用用户选择的pen和brush

● 使用所选的形状

● 调整座标。这样,方形和圆可以使用相同的width和height

覆盖paint方法需要两步:

● 在部件声明中增加paint方法的声明

● 在implementation部分写paint方法的实现

下面是paint方法的声明:

type

tsampleshape = class(tgraphiccontrol)

protected

procedure paint; override;

end;

然后,编写paint的实现:

procedure tsampleshape.paint;

begin

with canvas do

begin

pen := fpen;

brush := fbrush;

case fshape of

sstrectangle, sstsquare :

rectangle(0, 0, width, height);

sstroundrect, sstroundsquare:

roundrect(0, 0, width, height, width div 4, height div 4);

sstcircle, sstellipse :

ellipse(0, 0, width, height);

end;

end;

end;

无论任何控制需要更新图形时,paint就被调用。当控制第一次出现,或者当控制前面的窗口消失时,windows会通知控制画自己。也可以通过调用invalidate方法强制重画,就象stylechanged方法所做的那样。


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值