控件开发常见问题FAQ

I

import

Unregistered / Unconfirmed
GUEST, unregistred user!
目次
第一部分 简介
1.1 此份文档的目的为何?
第二部份 整合环境
2.1 在整合环境中如何找出构件所产生的问题?
2.2 如何检视Delphi所产生的汇编码?
2.3 我可以在运行时刻动态建立构件,但在设计时刻就会发生错误。为什麽?
2.4 如何撰写一个无法放置到表格上的构件?
2.5 在程序码编辑器中快速切换程序区段最简单的方法是什麽?
2.6 如何使我的构件在按下鼠标右键时出现快速功能选单?
2.7 为什麽构件在设计时刻会出现『I/O 103』的错误?
2.8 为什麽构件编辑器不会将构件属性的变动储存起来?
第三部分 在构件中使用其它构件
3.1 如何在构件中加入卷轴构件并让它在设计时刻能动作?
3.2 如何建立Windows95式样的卷轴?
第四部分 Bound Controls
4.1 哪里可以找得到有关TDataLink类的说明文档?
4.2 如何得知一个dataset中有几笔记录?
第五部分 VCL
5.1 使用整合环境调试时如何追踪检视VCL构件的程序码?
5.2 我的构件叁考到其它构件,如何得到叁考构件被消灭的讯息?
5.3 什麽是构件讯息?
5.4 我的构件得到输入焦点後仍不能接受键盘讯息,为什麽?
第六部分 其它信息
6.1 有哪些书介绍或讲解如何撰写构件?
6.2 有哪些Web站台可以取得撰写构件的信息?
第七部分 构件的储存与载入
7.1 如何将包含其它对象的对象一起存入DFM档?
7.2 如何得知构件是否正从数据流中读出?
7.3 如何确定构件的属性是否被正确地储存?
第八部分 工具程序
8.1 有没有Delphi版本的 YACC 及 LEX?
8.2 如何秀出JPEG格式图形档?
第九部分 基本编程技巧
9.1 如何建立不定数目的对象数组?
9.2 Delphi 2.0的 WinCrt 单元到哪去了?
9.3 自制构件时该从哪个类继承?
第十部分 进阶编程技巧
10.1 Delphi有与 C++ 一样的 I/O Stream 类吗?
10.2 如何取得列举型态变量的文字表示?
第十一部分 构件虚拟方法
11.1 如何得知构件的 window handle 是何时建立的?
11.2 如何得知是否表格上所有构件都已载入完成?
11.3 在哪里绘制构件最适合?
11.4 如何改变构件的窗口式样?
第十二部分 Windows API
12.1 构件卷动时闪动的很厉害,如何克服这种情况?
12.2 如何重新启动Windows?
12.3 如何快速大量地更改构件数据?
第十三部分 控制项边框
13.1 为什麽我的构件的 Ctl3D 属性设为 True 之後,它依然没有 3D 的边框呢?
13.2 如何实作 BorderStyle 属性?
第十四部分 控制项式样
14.1 当构件重绘时如何防止闪动的情况?
第十五部分 窗口消息
15.1 为什麽我的构件得不到方向键的讯息?
15.2 有没有与 Visual Basic『DoEvents』同样功能的函式?
--------------------------------------------------------------------------------
第一部分 简介
1.1 此份文档的目的为何?
这份文档的目的是为了解答有关撰写 Delphi构件时常见或文档上找不到的问题。我曾经花了一段很长的时间来了解探索 TDataLink 类,这让我觉得应该将撰写构件时常遇到的问题及经验心得写下来,分享给大家。不过我并不能保证写在这份文档里头的解答完全正确。如果你对其中的任何问题有更好的解决方法,或认为有什麽信息适合放在这份文档里的话,请告知作者。有任何错误或缺漏也欢迎指正。
除了再加上更多的问题及解答外,我试着再补充两个部分:
进阶编程师喜爱的工具:这也许跟构件设计没有直接的关系但至少它们跟 Delphi有关系。
值得叁考的文档刊物:由於空间的关系,这份文档不能放置太多的范例程序,因此叁考其它文档是十分需要的。这不是一份教材式的文档,我不会做太多条理式的说明,但会试着将最具有叁考价值的文献列出。
如果你有任何意见或建议,欢迎来信告诉我。
--------------------------------------------------------------------------------
第二部份 整合环境
2.1 在整合环境中如何找出构件所产生的问题?
我发现唯一能找出问题的方法只有:
在 Delphi 整合环境的 Tools|Options 对话框的 Library 页中将『Compile with debug info』选项打勾。
选 Component|Rebuild Library 重新编译构件库。
从 Turbo Debugger 中执行 Delphi。
选File|Change Dir移至包含构件程序码的目录下。
如果你的构件发生GPF时就可以检视堆栈然後得知到底是哪些发生问题了。
2.2 如何检视 Delphi 所产生的汇编码?
Glen Boyd 的回答:
开启登录编辑程序(REGEDIT.EXE),接着到『HKEY_CURRENT_USER2.0』下新增一个字符串机码『EnableCPU』,将它的字符串值设为『1』。此後Delphi整合环境的View选单下就会多一个『CPU』选项,它会开启一个窗口来检视目前程序指令的内存及汇编。你可以在侦错时利用单步追踪或其它方法来观察它。
2.3 我可以在运行时刻动态建立构件,但在设计时刻就会发生错误。为什麽?
你的构件必须继承自TComponent类或其衍生类。
你的构件建构函式及灭构函式宣告必须看起来像这样:
constructor Create(AOwner: TComponent); override;
destructor Destroy; override
所有在published区段宣告的字段型态必须是ordinal、single、double、extended 、comp、currency、string、small set(译注:指元素编号不超过0..31这个范围的集合;平常的集合可容许的范围为0..255)、method pointer或class其中一种。如果你宣告了其它型态的字段,Delphi编译器并不会检查出错误。然而当你使用这个构件时依然会得到一个GPF。
如果你想让TMyComponent构件可以在设计时刻操作,注意下面的宣告会引发十分严重的问题:
type TComplex = record
RealPart: Double;
ComplexPart: Double;
end;
class TMyComponent = Class(TComponent)
private
F1: TComplex;
published
property P1: TComplex read F1 write F1;
end;
2.4 如何撰写一个无法放置到表格上的构件?
Ray Lischner 的回答:
如果你不想让使用者将构件拉曳至表格上的话,使用 RegisterNoIcon 及 RegisterClass 过程来注册构件。
2.5 在程序码编辑器中快速切换程序区段最简单的方法是什麽?
Ray Konopka 的回答:
在探索 VCL 源码时,强烈建议你最好熟悉程序码编辑器里的书签功能。使用方法很简单:Ctrl-Shift-N,N 是从 0 至 9 的数字,用来配置一个书签。此後就可以使用 Ctrl-N 来跳跃至书签处。(译注:使用这项功能真的可以节省你许多来回卷动程序及找寻函式的时间,别迟疑了,快学吧!)
2.6 如何使我的构件在按下鼠标右键时出现快速功能选单?
你必须要建立一个构件编辑器。构件编辑器决定了构件在设计时刻时对鼠标键的反应及动作,你可以为构件定义它自己的快速功能选单。
建立构件编辑器的步骤大致如下:
从 TComponentEditor 类继承一个新的类。
改写类的 GetVerbCount、GetVerb及 ExecuteVerb方法。
在 Register 过程中使用 RegisterComponentEditor 过程来注册此构件编辑器。
有关构件编辑器这个主题在『Developing Delphi Components』这本书中有详尽的解说及信息。
2.7 为什麽构件在设计时刻会出现『I/O 103』的错误?
你可能在构件中使用了Writeln这个过程。
2.8 为什麽构件编辑器不会将构件属性的变动储存起来?
我发现有时自制的构件编辑器不会将构件属性储存起来。设计时刻一切正常,但是储存起来再重新读入後就有问题了。
原因是你很可能忘了在构件编辑器中调用此方法:
Designer.Modified;
如此一来Delphi才会知道你的构件编辑器更改过属性值了。
--------------------------------------------------------------------------------
第三部分在构件中使用其它构件
3.1 如何在构件中加入卷轴构件并让它在设计时刻能动作?
你的卷轴构件类必须处理 CM_DESIGNHITTEST 构件讯息才行。
TMyScrollBar = class (TScrollBar)
procedure CMDesignHitTest
(var Message: TCMDesignHitTest); message CM_DESIGNHITTEST;
end;
procedure TMyScrollBar.CMDesignHitTest( var Message: TCMDesignHitTest);
begin
Message.Result := 1;
end;
你的构件必须以以下方法建立卷轴:
TMyScrollBar.Create(nil);
而不是
TMyScrollBar.Create(Self);
3.2 如何建立Windows95式样的卷轴?
你必须配置卷轴的页面大小。你可以用以下的程序码来做:
procedure SetPageSize(ScrollBar: TScrollBar; PageSize: Integer);
var
ScrollInfo: TScrollInfo;
begin
ScrollInfo.cbSize := Sizeof (ScrollInfo);
ScrollInfo.fMask := SIF_PAGE;
ScrollInfo.nPage := PageSize;
SetScrollInfo (ScrollBar.Handle, SB_CTL, ScrollInfo, True);
end;
要取得目前页面大小可用如下方法:
function GetpageSize (ScrollBar: TScrollBar): Integer;
var
ScrollInfo: TScrollInfo;
begin
if HandleAllocated then
begin
ScrollInfo.cbSize := SizeOf (ScrollInfo);
ScrollInfo.fMask := SIF_PAGE;
GetScrollInfo (ScrollBar.Handle, SB_CTL, ScrollInfo);
Result := ScrollInfo.nPage;
end;
end;
--------------------------------------------------------------------------------
第四部分 Bound Controls
4.1 哪里可以找得到有关 TDataLink 类的说明文档?
我可以大胆地说全世界有关 TDataLink 的说明文档只有一份,就在这儿:
属性 (Property) 介绍
property Active: Boolean(唯读)
当此 DataLink 连结至一个已开启的 DataSource 时会传回 True。当 Active 状态改变时会 触发ActiveChanged方法。
property ActiveRecord: Integer(可读写)
用来配置或取得 DataLink 缓冲区中目前所指向的记录代码,代码的范围是 0 .. BufferCount - 1。使用它来配置记录代码时必须小心不要超过这个范围,否则可能导致不可预期的错误。
property BufferCount: Integer(可读写)
DataLink 拥有一个数据缓冲区。而 BufferCount 属性即用来配置或取得缓冲区大小,缓冲区大小决定了一个dataset同时可以显视的数据记录笔数。对大部分的数据感知元件来说,BufferCount 的值是 1;但对 TDataGrid 来说,BufferCount 代表它的可视列数目。
property DataSet: TDataSet(唯读)
传回此 DataLink 所连结的 DataSet。其实就是 DataSource.DataSet。
property DataSource: TDataSource(可读写)
传回此DataLink所连结的DataSource。
property DataSourceFixed: Boolean(可读写)
这个属性可用来防止 DataSource 属性被更改。如果此属性设为 True,当我们试着改变 DataSource 属性时会引发一个例外。
property Editing: Boolean(唯读)
如果 DataLink 正处於编辑状态则传回 True。
property ReadOnly: Boolean(可读写)
配置 DataLink 是否为唯读状态。这个属性并不会影响所连结的 DataSet。在唯读状态下这个 DataLink 无法进入编辑状态。
property RecordCount: Integer(唯读)
传回DataSet的数据记录数目。
方法 (Method) 介绍
function Edit: Boolean;
让所连结的DataSet进入编辑状态。传回值: 成功传回 True ,失败传回 False
procedure UpdateRecord;
我们不直接调用这个方法,它是提供其它程序来调用的。这个方法只有配置一个旗帜然後调用 UpdateData 方法。
虚拟方法 ( Virtual Method )
要让 TDataLink 对象与构件沟通必须改写下列这些方法:
procedure ActiveChanged
当连结的 DataSource 开启状态改变时会调用此方法。使用 Active 属性可以得知目前是否为开启状态。
procedure CheckBrowseMode
数据库有任何改变之後都会先调用这个方法。
procedure DataSetChanged;
当下列任一事件发生时都会调用此方法:
移至DataSet的开头
移至DataSet的结尾
在DataSet中插入或新增数据
删除DataSet的数据
取消DataSet的编辑
更新记录
如果不想改写这个方法只要在其中调用:
RecordChanged(nil);
procedure DataSetScrolled(Distance: Integer)
每当目前记录变更时会调用此方法。Distance 叁数代表缓冲区欲卷动的行数。(其值范围皆在 -1 .. 1 之间)。使用 ActiveRecord 属性可以取得缓冲区中目前所指向的记录。我们无法强制让 DataLink 的缓冲区卷动。
procedure FocusControl(Field: TFieldRef)
与TField.FocusControl方法相同。
procedure EditingChanged
当 DataLink 的编辑状态改变时会调用此方法。使用 Editing 属性可以得知DataLink 是否 正处於编辑状态。
procedure LayoutChanged
当 DataSet 的 Layout 改变时会调用此方法(例如新增一个column)。
procedure RecordChanged(Field: TField)
当下列任一事件发生时都会调用此方法:
目前记录进入编辑状态
目前记录内容更动
procedure UpdateData
在一笔记录被更新以前会调用此方法。你可以调用 Abort 过程来防止数据库更新。
4.2 如何得知一个 dataset 中有几笔记录?
TDateSet 的 RecNo 属性可以传回数据记录的数目,但很不幸地它只适用於 dBase 及 Paradox 的数据表格。若想得知目前数据记录的编号,可以从 TDataLink 类衍生一个新的类,然後进行下 列步骤:
改写 DataSetScrolled 方法以取得目前记录是否被卷动。
改写 DataSetChanged 方法来确认目前记录是否跳至最前面或最後面了。
接着你可以将这个新类的对象连结到TDataSource对象上然後就可以随时得知目前的记录编号了。
--------------------------------------------------------------------------------
第五部分 VCL
5.1 使用整合环境调试时如何追踪检视 VCL构件的程序码?
将你想要追踪的 VCL 原始程序单元拷贝至存放专案的目录中并重新编译构件库,此後你就可以在那些 VCL单元中追踪检视程序码了。
5.2 我的构件叁考到其它构件,如何得到叁考构件被消灭的讯息?
Max Nilson 的回答:
TComponent 类提供了 Notification 方法。当一个构件被移除时我们可以利用这个方法得到消息以进行适当的反应。你可以叁考『Component Writer's Guide』内有关 Notification 及FreeNotification 这两个方法的说明。
当你的构件叁考到另一个构件,例如,你的构件中有一个 TDataSource 型态的属性。那 你必须改写此构件的 Notification 方法,在其中检查被移除的构件是否就是本身所叁考的构件。预设情况下,当构件被移除时,所有其它在同一个表格上的构件才会收到消息,如果叁考构件位於另一个表格上时,你的构件无法得知这件事情。Delphi 2.0 推出了TDataModule,叁考构件位於另一个表格上的机会大幅增加,所以你应该利用 FreeNotification 方法来确定当叁考构件移除时,你一定可以得到消息。
如果你不改写 Notification 方法来处理叁考构件被移除的讯息,这会让 Delphi整合环境陷入十分不稳定的状态。它可能不会立刻当掉,但你也不能再正常地继续其它工作了。
下面是一个范例,当你的构件叁考其它构件时,千万记得要做以下的处理:
TMyComponent = class (TComponent)
private
FDataSource: TDataSource;
procedure SetDataSource(Value: TDataSource);
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
published
property DataSource: TDataSource read FDataSource write SetDataSource;
end;
procedure TMyComponent.SetDataSource(Value: TDataSource);
begin
if Value <> FDataSource then
begin
FDataSource := Value;
// 告诉叁考构件说,当它被移除时记得通知我一声。
if FDataSource <> nil then FDataSource.FreeNotification(Self)
end;
end;
procedure TMyComponent.Notification(AComponent: TComponent; Operation:TOperation);
begin
inherited Notification(AComponent, Operation);
// 如果被移除的正是叁考构件,把FDataSource字段清除。
if (Operation = opRemove) and (AComponent = FDataSource) then
FDataSource := nil
end;
5.3 什麽是构件讯息?
构件讯息是什麽?它十分类似Windows的窗口消息,只有一点不同:构件讯息只适用於 VCL 构件;而窗口消息可以用在系统内所有具有 window handle 的控制项或窗口。如果你有一个具有 Font 属性的构件(例如TLabel构件),当我们更改它的 Font 属性时并没有送出窗口消息(译注:TLabel 构件不是窗口控制项,根本也没有窗口 Handle可以让我们传送窗口消息),但是控制项仍然知道字体改变了所以要重画自己,为什麽?因为我们有构件讯息。
构件讯息不可以由虚拟方法来处理,这可能是设计 VCL 时的考量,大概是因为不想让虚 拟方法表格(Virtual Method Table)过於庞大的原因。
『Secrets of Delphi 2.0』这本书对於所有的构件讯息有十分详尽的解说。
接下来我们列出一些比较常见的构件讯息及它们的作用。标示着『Notification Only』 的讯息表示送出这个讯息只是为了通知构件某件消息而己,并不传入任何叁数而且也不需要传回值。
CM_ACTIVATE (Notification Only)
当表格成为焦点窗口时会传给本身这个讯息。
CM_CTL3DCHANGED (Notification Only)
当控制项的Ctl3D属性更改时会传给本身这个讯息。
CM_DESIGNHITTEST 叁数:TCMDesignHitTest 传回值:0或1
在设计时刻当鼠标移到构件上头时,整合环境会送给此构件这个讯息。此讯息的目的用来决定构件在设计时刻是否要处理鼠标讯息。如果传回值是 1,整合环境就让构件自行处理鼠标讯息;若传回值是 0,则整合环境会帮你处理鼠标讯息。如果传回值永远是 1,那麽构件的快速功能选单则永远不会出现;如果构件不处理这个讯息或永远传回 0,那此构件在设计时刻将无法对鼠标讯息做任何反应。
CM_FONTCHANGED (Notification Only)
控制项的字体改变後送给本身此讯息。
CM_FONTCHANGE (Notification Only)
当控制项收到WM_FONTCHANGE窗口消息时会送给本身这个讯息。
CM_PARENTCTL3DCHANGED (Notification Only)
当构件父控制项的Ctl3D属性改变或配置新的父控制项时会收到此讯息。
CM_PARENTCOLORCHANGED (Notification Only)
当构件父控制项的 Color 属性改变或配置新的父控制项时会收到此讯息。
CM_PARENTFONTCHANGED (Notification Only)
当构件父控制项的Font属性改变或配置新的父控制项时会收到此讯息。
CM_PARENTSHOWHINTCHANGED (Notification Only)
当构件父控制项的ShowHint属性改变或配置新的父控制项时会收到此讯息。
CM_WININICHANGE 叁数:TWMWinIniChange 传回值:无
当控制项收到WM_WININICHANGE窗口消息时会送给本身这个讯息。
5.4 我的构件得到输入焦点後仍不能接受键盘讯息,为什麽?
如果你的构件有 DragMode 属性而且将它设成 dmAutomatic 时,很有可能让你的构件以为它正被拖放但实际上并没有的情况。在 Controls 单元中有一个区域变量 DragControl 指 向目前正被拖放的构件。你遇到的情况很可能就是明明没有拖放的动作但是DragControl 变量却指向你的构件。在 TWinControl 的 WndProc 方法中,当 DragControl 变量指向构件本身时,会忽略所有键盘讯息,这就是原因了!
--------------------------------------------------------------------------------
第六部分 其它信息
6.1 有哪些书介绍或讲解如何撰写构件?
有关撰写构件的『标准』叁考书籍:
『Developing Delphi Components』 作者:Ray Konopka 出版:Coriolis Group
下面这本书并不专注於构件写作,但里面提到许多构件撰写者不可不知的信息:
『Secrets of Delphi 2』 作者:Ray Lischner 出版:Waite Group
另外一本构件撰写的好书,它有许多在『Developing Delphi Components』里找不到的信息:
『Programming Delphi Custom Components』 作者:Fred Bulback 出版:M&T Books
6.2 有哪些Web站台可以取得撰写构件的信息?
全世界最大的 Delphi Web 站台『Delphi SuperPage』
(译注:亚洲地区使用者可以就近到位於日本的 Mirror Site )
我在下面这些站台中找到许多构件的源码:
Temple of Delphi
Delphi Free Stuff
(译注:『Your Delphi Programming Resource』整理构件也十分用心! )
(译注:台湾地区目前维持最好的 Delphi 站台是『32 Bit Delphi 深度历险』及其 Mirror Site)
你也可以使用一些搜寻引擎来寻找有关 Delphi 的站台:
Yahoo
Alta Vista
(译注:Excite 搜寻引擎也别错过罗!)
--------------------------------------------------------------------------------
第七部分 构件的储存与载入
7.1 如何将包含其它对象的对象一起存入 DFM 档?
我试过许多方法,包括改写构件的 DefineProperties及 WriteComponents方法,但都还是失败了。所以我只能说要解决这个问题的话只有使用 Delphi 原本的方法一途。
将包含其它对象的对象一起储存起来的步骤大致如下:
确定你要储存的所有对象都是从 TComponent 类衍生下来的。
将所有需要储存的变量宣告在 published 区段。
在构件的 Register 过程中调用 RegisterComponents过程来注册所有你要储存起来的类。
每个拥有子控制项的类必须改写 GetChildren 方法以储存每个子控制项。(在 Delphi 1.0 中你必须改写 WriteComponents 方法并且为每个子控制项调用 WriteComponent方法)。
将对象载入的方法用了点小技巧。你必须改写构件的 GetChildOwner 及 GetChildParent 方法,否则 Delphi会将所有对象的拥有者都配置为构件所在的表格。(在Delphi 1.0 中你必须改写ReadState方法)。
7.2 如何得知构件是否正从数据流中读出?
当构件正从数据流中读出时,它的 ComponentState 属性会包含csLoading 旗帜。
constructor TMyClass.Create(AOwner: TComponent);
begin
if csLoading in AOwner.ComponentState then
begin ... end
else
begin ... end;
end;
7.3 如何确定构件的属性是否被正确地储存?
有许多很简单的方法可以验证属性是否被正确地储存在文件里:
在整合环境中用鼠标右键点选表格然後选择『View as Text』。然而万一 DFM 档 有任何错误的话,你将什麽也看不到。
开个 DOS 窗口,利用 Delphi 所附的『Convert』程序将 DFM 档转成文字格式。
Stefan Hoffmeister 指出复制或剪下构件後,到任何一个文书编辑器(如记事本)中贴上,你就可以看到此构件的文字表示。你甚至可以编辑这些文字表示後再将它贴回 Delphi整合环境的表格上。
--------------------------------------------------------------------------------
第八部分 Delphi 的工具
8.1 有没有Delphi版本的 YACC 及 LEX?
有。Albert Graef 这位仁兄写了 Turbo Pascal 版本的 YACC 及 LEX,也可以让 Delphi 使用。
你可以在 ftp://ftp.simtel.net/pub/simtelnet/msdos/turbopas 下取得 tply30a1.zip 及 tply30a2.zip 这两个文件,其中还包含这两个工具的原始程序哦!
8.2 如何秀出 JPEG 格式图形档?
Jacques Nomssi Nzali 将 Independent JPEG Group 所发展的免费 JPEG 库改写成 Pascal 版本。你可以从下取得:
PASJPG10.ZIP
Independent JPEG Group 的免费 JPEG 函式库 rev 6a 之 Pascal 版本 (1.0 版)。
--------------------------------------------------------------------------------
第九部分 基本编程技巧
9.1 如何建立不定数目的对象数组?
最简单的方法是使用 TList 类。我发现从 TList 衍生一个新类很有用处。接下来的程序码示范如何为一个特定型态撰写一个特别的 TList 类,并且加进基本的错误检查。
TListOfMyObject = class (TList)
private
function GetItems(Index: Ordinal): TMyObject;
public
property Items[Index: Ordinal]: TMyObject read GetItems;
procedure Add(AObject: TMyObject);
end;
function TListOfMyObject.GetItems (Index: Ordinal): TMyObject;
begin
if Index >= Count then
raise Exception.CreateFmt('Index(%d) outside range 1..%d', [Index, Count-1]);
Result := inherited Items[Index];
end;
procedure TListOfMyObject.Add (AObject: TmyObject);
begin
inherited Add(AObject);
end;
9.2 Delphi 2.0的 WinCrt单元到哪去了?
Delphi 2.0并没有 WinCrt单元。 先别伤心,这是因为我们可以用其它方法来取代它。在 Project|Options 的 Linker 页次中将『Generate console application』选项打开,你就可以像以前使用 WinCrt 单元一样地写程序了!
9.3 自制构件时该从哪个类继承?
VCL 中有一些『自订』类,而且有许多控制项是直接由这些『自订』类继承下来的。例如 TMemo 直接继承自 TCustomMemo类。这些自订类写好了所有该控制项所拥有的功能,只是没有将属性公开出来而己。大部分情形下,你应该从那些自订类继承而不是控制项类。
如果你要从头撰写自己的构件,那麽从 TCustomControl 类继承是个不错的主意。撰 写出来的构件会具有 Window Handle 且可以接受输入焦点。
另外根据你的需要也可以从这些类继承:
TGraphicControl:视觉构件,但是没有window handle,也不能接受输入焦点。
TComponent:不可视构件,你没办法在运行时刻看到它。
TWinControl:将已存在的窗口构件包装起来,如Windows标准控制项或VBX构件。
--------------------------------------------------------------------------------
第十部分 进阶编程技巧
10.1 Delphi 有与 C++ 一样的 I/O Stream 类吗?
答案可以说有也可以说没有。Delphi允许你建立自己的『文字档设备驱动过程』,它可以让你使用Delphi 标准的 I/O 函式库来处理非标准的 I/O,如处理 UNIX 格式的文字档或处理 Socket 所取得的数据。虽然没有像 C++ 的 I/O Stream 类那麽强大但应该也足够一般用途使用了。
建立『文字档设备驱动过程』的方法在『Object Pascal Language Guide』中有明述。此 外你也可以叁考 VCL 的 Printer 单元。
Delphi有 TStream 类,不过是设计用来将对象写入数据流的,不像 C++ 的 I/O Stream 类那麽具有弹性。
10.2 如何取得列举型态变量的文字表示?
使用 TypInfo单元中的 GetEnumName 函式:
type
TMyType = (Value1, Value2);
var
TypeValue: TMyType;
begin
Writeln (GetEnumName(TypeInfo(TMyType), Ord(TypeValue));
end;
TypInfo单元中还有许多与型别信息有关的函式。
『Secrets of Delphi 2.0』这本书有许多关於TypInfo单元的信息,值得叁考。
--------------------------------------------------------------------------------
第十一部分 构件虚拟方法
11.1 如何得知构件的window handle是何时建立的?
控制项的 window handle 是在 CreateWnd 方法中建立的。如果你想要在建立 window handle 後接着做某些动作那麽你应该改写 CreateWnd 方法:
procedure TMyClass.CreateWnd;
begin
// 现在还没取得 window handle
inherited CreateWnd;
// 调用 inherited 以取得 window handle
// 在这里撰写你想要执行的动作
end;
11.2 如何得知是否表格上所有构件都已载入完成?
Loaded 方法是在载入完成後接着被调用的。
procedure TMyClass.Loaded;
begin
inherited Loaded;
// 将ComponentState中的 csLoading 状态清除
// 在这里撰写你想要执行的动作
end;
11.3 在哪里绘制构件最适合?
你应该拦截 WM_PAINT 窗口消息然後利用 Canvas 来绘制构件。然而 VCL 己经帮你拦 截好了,你只须改写构件的 Paint 方法即可。
procedure TMyClass.Paint;
begin
// 如果你的构件是己存在的构件继承下来的,那麽必须在这里调用 inherited Paint
inherited Paint
// 在这里撰写你想要执行的动作
end;
11.4 如何改变构件的窗口式样?
CreateParams方法用来配置构件的窗口式样及其它必须传递至 CreateWindowEx API 的 叁数。要改变构件的窗口式样,例如增加或拿掉构件的垂直卷轴只要改写 CreateParams 方法:
procedure TMyControl.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if IWantAScrollBar then
Params.Style := Params.Style or WS_VSCROLL
else
Params.Style := Params.Style and not WS_VSCROLL;
end;
--------------------------------------------------------------------------------
第十二部分 Windows API
12.1 构件卷动时闪动的很厉害,如何克服这种情况?
要卷动构件本身最简单的方法就是改变它的座标然後重画构件,但是这方法会导致构件闪动的很厉害。
比较好的方法是调用 ScrollWindow 或 ScrollWindowEx Windows API。
闪动的另一个原因可能来自於 WM_PAINT 及 WM_ERASEBKGND。你可以试着拦截 WM_ERASEBKGND 及 WM_PAINT 讯息然後自己处理绘图动作,包括绘制背景的动作,或许可以改善闪动的情况。
12.2 如何重新启动Windows?
使用 ExitWindowsEx Windows API。
12.3 如何快速大量地更改构件数据?
在进行大量数据更改前後,利用 WM_SETREDRAW 讯息来控制你的构件暂时不要重画,这不但可以使数据配置速度增快也防止构件闪烁的情况。
--------------------------------------------------------------------------------
第十三部分 控制项边框
13.1 为什麽我的构件的 Ctl3D 属性设为 True 之後,它依然没有 3D 的边框呢?
如果 ControlStyle 属性内没有包含 csFramed 旗帜那麽 Ctl3D 属性就会没有作用。在构件 的建构函式内加上:
ControlStyle := ControlStyle + [csFramed];
13.2 如何实作 BorderStyle 属性?
在控制项配置有没有边框之後要重新建立 window handle:
FBorderStyle: TBorderStyle;
procedure SetBorderStyle(Style: TBorderStyle);
property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle;
procedure CreateParams(var Params: TCreateParams); override;
procedure TMyControl.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if FBorderStyle = bsSingle then
Params.Style := Params.Style or WS_BORDER
else
Params.Style := Params.Style and not WS_BORDER;
end;
procedure TMyControl.SetBorderStyle(Style: TBorderStyle);
begin
if Style <> FBorderStyle then
begin
FBorderStyle := Style;
// 重新建立window handle
RecreateWnd;
end;
end;
--------------------------------------------------------------------------------
第十四部分 控制项式样
14.1 当构件重绘时如何防止闪动的情况?
如果构件的 ComponentStyle 属性没有包含 csOpaque 旗帜的话,调用 Invalidate方法时 会导致构件的背景先被擦掉再重绘。如果你在 Paint 方法中绘制背景,那你应该在构件的建构函式中加上:
ComponentStyle := ComponentStyle + [csOpaque];
Max Nilson的回答:
引起闪动另一个原因可能是 WM_ERASEBKGND 讯息的处理。当 VCL 控制项收到一个 WM_ERASEBKGND 讯息时,它会将构件的背景擦掉然後配置成预设的颜色。如果你的元件衍生自 TWinControl,而且构件的颜色与背景颜色不同(例如图形),每次重画以前都会将构件先清成背景颜色再重绘,这就是造成闪动的原因了!
解决的方法不难,你必须告诉 Windows 你要自行解决『所有的』绘图动作。不过有一个前提是,你一定要确定你的 Paint 方法将整个构件都画过,如果你漏了什麽地方忘了画,那个部分的数据会由乱数组成,你能想见这情况吗?使用这个方法可以加速你的构件绘制动作(稍微快一点点),因为少了一个填满背景颜色的动作。
type
TMyComponent = class (TWinControl)
...
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
...
end;
procedure TBMyComponent.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
// 不要重绘背景,这会造成构件闪动
Message.Result := 0
end;
--------------------------------------------------------------------------------
第十五部分 窗口消息
15.1 为什麽我的构件得不到方向键的讯息?
你必须拦截 WM_GETDLGCODE 才能处理方向键的讯息,在 WM_GETDLGCODE 的讯息处理 者中传回 DLGC_WANTARROWS。如果你不这样做,那方向键的功用就只能用来移动窗口焦点而己。
Max Nilson 的回答:
想要你的构件能够处理方向键,你必须要拦截 CM_WANTSPECIALKEY 构件讯息。 CM_WANTSPECIALKEY 构件讯息提供你比拦截 WM_GETDLGCODE 窗口消息更容易且灵活的判断方法来决定是否需要某些特殊键的讯息。当控制项收到任何一个特殊键时就会送出CM_WANTSPECIALKEY 构件讯息给控制项。
特殊键包括:VK_TAB、VK_LEFT、VK_RIGHT、VK_UP、VK_DOWN、VK_RETURN、VK_EXECUTE 、VK_ESCAPE 及 VK_CANCEL。如果讯息传回值是非零值,这个键就会被送至 KeyPress 方法以供处理,否则这个键的讯息会被送至构件的父控制项,以预设方式来处理。
一个简单的范例:
type
TMyComponent = class (TWinControl)
...
protected
procedure CMWantSpecialKey(var Message: TCMWantSpecialKey); message CM_WANTSPECIALKEY;
...
end;
procedure TMyComponent.CMWantSpecialKey(var Message: TCMWantSpecialKey); begin
inherited;
// 我们只想处理向左方向键,其它的特殊键都给 Windows 处理
if Message.CharCode = VK_LEFT then
Message.Result := 1;
end;
CM_WANTSPECIALKEY 构件讯息比 WM_GETDLGCODE 讯息更具有弹性的地方在这儿。我们甚至可以根据是按下的是哪个特殊键才决定是否处理这个键。例如,你的控制项有三张图像,你可以让使用者利用左右方向键来回检视它们,如果翻到最後一张图像再按向右键时,焦点就让它离开构件,剩下的全部都让 Delphi 来处理。
15.2 有没有与 Visual Basic『DoEvents』同样功能的函式?
有。Application.ProcessMessages方法。
 
顶部