第十九章-Delphi自定义部件开发(二)(4)

⑵ 定义处理过程类型

一旦你决定产生事件,就要定义事件如何被处理,这就是要决定事件处理过程的类型。在大多数情况下,定义的事件处理过程的类型是简单的通知类型(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 <> #0 then { 执行缺省处理 } ;

实际的代码将与这稍有不同,因为它只处理窗口消息,但处理逻辑是相同的。在缺省情况下,部件先调用任何用户赋予的事件处理过程,然后执行标准处理。如果用户的事件处理过程将key设为空,则部件跳过缺省处理。

19.2.2.3 处理消息

在传统Windows编程中,一个很关键的方面是处理Windows发送给应用程序的消息。Delphi已经帮你处理了大多数的普通消息,但是在创建部件的过程中有可能Delphi没有处理方法,得由自己处理消息,也可能创建了新的消息需要处理它们。

学习掌握Delphi的消息处理,要掌握以下三个方面:

● 理解消息处理系统

● 修改(改变)消息处理方法

● 建立新的消息处理方法

1. 理解消息处理系统

所有的Delphi对象内部具有处理消息的机制,如调用消息处理方法或消息处理过程。消息处理的基本思想是对象接收某种消息并派送它们,这是通过调用与接收的消息相应的方法来实现的,如果没有相应于消息的指定的方法,那就调用缺省处理。下面的图解表示消息派送系统:

Delphi部件库定义了将所有Windows消息(包括用户自定义消息)直接转换到对象方法调用的消息派送系统。一般没有必要改变这种消息派送系统,只要建立消息处理方法。

⑴Windows消息中有什么?

Windows消息是包含若干有用的域的数据记录。记录中最重要的是一个整型大小的值,该值标识消息。Windows定义了大量的消息。库单元Messages声明了所有消息的标识。消息中其它的有用信息包括两个域参数和结果域。两个参数分别是16位和32位的。Windows代码总是以wParam和lParam来引用它们。

最初,Windows程序员不得不记住包含的每一个参数。现在,微软公司已经命名了这参数。这样理解伴随这些消息的信息就更简单了。例如,WM_KEYDOWN消息的参数被称为vkey和keydata,这就比wParam和lParam给出了更多的描述信息。

Delphi为不同类型的消息定义了指定的记录类型。如鼠标消息在long参数中传递鼠标事件的x、y座标,一个在高字,一个在低字。使用鼠标消息记录,你不需要自己关心哪个字是哪个座标,因为引用这些参数时通过名子Xpos和Ypos取代了lParamLo和lParamHi。

⑵ 派送方法

当应用程序创建窗口时,在Windows Kernel中注册了一个窗口过程。窗口过程是处理窗口消息的函数。传统上,窗口过程包括了Case表达式,表达式的每个入口是窗口要处理的每一条消息。当你每次创建窗口时,必须建立完整的窗口过程。

Delphi在下列三方面简化了消息派送:

● 每个部件继承了完整的消息派送系统

● 派送系统具有缺省处理。用户只需定义想响应的消息的处理方法

● 可以修改消息处理的一部分,依靠继承的方法完成大多数处理

这种消息派送系统的最大优点是用户能在任何时候安全地发送任何消息给任何部件。如果部件没有为该消息定义处理方法,那缺省处理方法会解决这个问题,通常是忽略它。

Delphi为应用程序每种类型的部件注册了名为MainWndProc的方法作为窗口过程。MainWndProc包含了异常处理块,它完成从Windows到名为WndProc的虚方法传送消息记录,并且通过调用应用程序对象的HandleException方法处理异常。

MainWndProc是静态方法,没有包含任何消息的指定处理方法。定制过程发生在WndProc中,因为每个部件类型都能覆盖该方法以适合特定的需要。

WndProc方法为每个影响它们处理的任何条件进行检查,以捕捉不要的消息。例如,当被拖动时,部件忽略键盘事件,因此,TWinControl的WndProc只在没有拖动时传送键盘事件。最后WndProc调用Dispatch方法,该方法是从TObject继承来的静态方法,决定什么方法来处理消息。

Dispatch使用消息记录的Msg域来决定怎样派送特定消息。如果部件已经给该消息定义了处理方法,则Dispatch调用该方法,反之,Dispatch调用缺省处理方法。

2. 改变消息处理方法

在改变自定义部件的消息处理方法之前,先要弄清楚你真正想要做什么。Delphi将大多数的Windows消息转换成部件编写者和部件用户都能处理的事件。一般来说,你应当改变事件处理行为而不是改变消息处理行为。

为了改变消息处理行为,要覆盖消息处理方法。也能提供捕获消息防止部件处理该消息。

⑴ 覆盖处理方法

为了改变部件处理特定消息的方法,要覆盖那个消息的处理方法。如果部件不处理该消息,你就需要声明新的消息处理方法。

为了覆盖消息处理方法,要在部件中以相同的消息索引声明新的方法。不要使用override指令,你必须使用Message指令和相应的消息索引。

例如,为了覆盖一个处理WM_PAINT消息的方法,你要重声明WMPaint方法:

type

TMyComponent=class(…)

procedure WMPaint(var Message: TWMPaint); message WM_PAINT;

end;

⑵ 使用消息参数

在消息处理方法内部,自定义部件访问消息记录的所有参数。因为消息总是var参数,如果需要的话,事件处理过程可以改变参数的值。Result域是经常改变的参数。Result是Windows文档中所指的消息的返回值:由SendMessage返回。

因为消息处理方法的消息参数的类型随着被处理的消息的变化而变化,所以应当参考Windows消息文档中的参数的名字和含义。如果出于某种原因要使用旧风格的消息参数(wParam、lParam),可以配合通用类型TMessage来决定Message。

⑶ 捕获消息

在某种情况下,你可能希望自定义部件能忽略某种消息。就是说,阻止部件将该消息派送给它的处理方法。为了那样来捕获消息,可以覆盖虚方法WndProc。

WndProc方法在将消息传给Dispatch方法前屏蔽该消息。它依次决定哪一个方法来处理消息。通过覆盖WndProc,部件得到了派送消息之前过滤它们的机会。

通常,象下面这样覆盖WndProc:

procedure TMyControl.WndProc(var Message: TMessage);

begin

{ 决定是否继续处理过程 }

inherited WndProc (Message);

end;

下面的代码是TControl的WndProc的一部分。TControl定义整个范围内的鼠标消息,当用户拖动和放置控制时,它们将被滤过。

procedure TControl WndProc(var Message:TMessage);

begin

if (Message.Msg >= WM_MOVSEFIRST) and

(Message.Msg <= WM_MOUSELAST) then

if Dragging then

DragMouseMsg(TWMMOUSE(Message)) { 处理拖动 }

else

… { 正常处理其它 }

… { 否则正常处理 }

end;

时间: 2024-08-03 14:44:13

第十九章-Delphi自定义部件开发(二)(4)的相关文章

第十九章-Delphi自定义部件开发(一)(1)

Delphi除了支持使用可视化部件所见即所得地建立应用程序外,还支持为开发应用而设计自己的部件. 在本章中将阐述如何为Delphi应用程序编写部件.这一章将达到两个目的: ● 教你如何自定义部件 ● 使你的部件成为Delphi环境的有机组合部分 19.1 Delphi部件原理 19.1.1 什么是部件 部件是Delphi应用程序的程序构件.尽管大多数部件代表用户界面的可见元素,但部件也可以是程序中的不可见元素,如数据库部件.为弄清什么是部件可以从三个方面来考察它:功能定义.技术定义和经验定义.

第十九章-Delphi自定义部件开发(三)(3)

编译过的Help文件和关键词文件应当与库单元在同一目录. ① 建立Help文件 你可以使用任何的工具创建Windows Help文件.Delphi的多文件搜索引擎,可以包含任何数目的Help文件的要素.在编译的Help文件之外,你应当拥有RTF源文件,这样才能生成关键词文件. 为使自定义部件的Help同库中其它部件一起工作,要遵循下列约定: ● 每个部件有占一页的帮助 部件帮助页应当给出部件目的的简单描述,然后列出最终用户可用的属性.事件和方法的描述.应用开发者通过在窗体上选择部件并按F1访问这

第十九章-Delphi自定义部件开发(三)(2)

19.2.2.4 注册部件 编写部件及其属性.方法和事件只是部件创建过程的一部分.尽管部件具有这些特征就可用,但部件真正功能强大的是在设计时操作它们的能力. 使部件在设计时可用需要经过如下几步: ● 用Delphi注册部件 ● 增加选择板位图 ● 提供有关属性和事件的帮助 ● 存贮和读取属性 1. 用Delphi注册部件 为了让Delphi识别自定义部件,并将它们放置于Component Palette上,你必须注册每一个部件. 注册一个部件要在部件所在单元里加入Register方法,这包括两个

第十九章-Delphi自定义部件开发(三)(1)

3. 创建新的消息处理方法 因为Delphi只为大多数普通Windows消息提供了处理方法,所以当你定义自己的消息时,就要创建新的消息处理方法. 用户自定义消息的过程包括两个方面: ● 定义自己的消息 ● 声明新的消息处理方法 ⑴ 定义自己的消息 许多标准部件为了内部使用定义了消息.定义消息的最一般的动因是广播信息和状态改变的通知. 定义消息过程分两步: ● 声明消息标识符 ● 声明消息记录类型 ① 声明消息标识 消息标识是整型大小的常量.Windows保存了小于1024的消息用于自己使用,因此

第十九章-Delphi自定义部件开发(二)(3)

这三句表达式使用RegisterPropertyEditor三种不同的用法: ● 第一种最典型 它注册了用于所有TComponent类型属性的属性编辑器TComponentProperty.通常,当为某种类型属性注册属性编辑器时,它就能应用于所有这种类型的属性,因此,第二和第三个参数为nil. ● 第二个表达式注册特定类型的属性编辑器 它为特定部件的特定属性注册属性编辑器,在这种情况下,编辑器用于所有部件的Name属性. ● 第三个表达式介于第一个和第二个表达式之间 它为部件TMenu的TMen

第十九章-Delphi自定义部件开发(二)(2)

⑸ 缺省属性值 当声明一个属性,能有选择地声明属性的缺省值.部件属性的缺省值是部件构造方法中的属性值集.例如,当从Component Palette选择某部件置于窗体中时,Delphi通过调用部件构造方法创建部件,并决定部件属性初始值. Delphi使用声明缺省值决定是否将属性值存在DFM文件中.如果不描述缺省值,Delphi将总是保存该属性值.声明缺省值的方法是在属性声明后加default指令,再跟缺省值. 当重声明一个属性时,能够描述没有缺省值的属性.如果继承的属性已有一个,则设立没有缺省值

第十九章-Delphi自定义部件开发(一)(3)

19.2.1.5 编写部件的面向对象技术 部件使用者在Delphi环境中开发,将遇到在包含数据和方法的对象.他们将在设计阶段和运行阶段操作对象,而编写部件将比他们需要更多的关于对象的知识,因此,你应当熟悉Delphi的面向对象的程序设计. 1. 建立部件 部件用户和部件编写者最基本的区别是用户处理对象的实例,而编写者创建新的对象类型.这个概念是面向对象程序设计的基础.例如,用户创建了一个包含两个按钮的窗体,一个标为OK,另一个标为Cancel,每个都是TButton的实例,通过给Text.def

第十九章-Delphi自定义部件开发(一)(2)

2. 建立原始控制 标准控制是在运行时可见的.这些标准控制都从TWinControl,继承来的,当你建立原始控制时,你使用TCustomControl作为起始点.标准控制的关键特征是它具有窗口句柄,句柄保存在属性Handle中,这种控制: ● 能接受输入焦点 ● 能将句柄传送给Windows API函数 如果控制不需要接受输入焦点,你可把它做成图形控制,这可能节省系统资源. 3. 建立图形控制 图形控制非常类似定制的控制,但它们没有窗口句柄,因此不占有系统资源.对图形控制最大的限制是它们不能接收

第十九章-Delphi自定义部件开发(二)(1)

19.2.2 Delphi部件编程 19.2.2.1 创建属性 属性(Property)是部件中最特殊的部分,主要因为部件用户在设计时可以看见和操作它们,并且在交互过程中能立即得到返回结果.属性也很重要,因为如果将它们设计好后,将使用户更容易地使用,自己维护起来也很容易. 为了使你在部件中更好地使用属性,本部分将介绍下列内容: ● 为什么要创建属性 ● 属性的种类 ● 公布(publishing)继承的属性 ● 定义部件属性 ● 编写属性编辑器 1. 为什么要创建属性 属性提供非常重要的好处,最