Delphi 组件撰写常问问题


  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_USER\Software\Borland\Delphi\2.0\Debu gging』下新增一个字符串机码『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 Writers 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 搜寻引擎也别错过

你可能感兴趣的:(Delphi)