Lazarus For Delphi Users/zh CN
│
Deutsch (de) │
English (en) │
español (es) │
français (fr) │
日本語 (ja) │
한국어 (ko) │
português (pt) │
русский (ru) │
slovenčina (sk) │
中文(中国大陆) (zh_CN) │
Delphi 用户的 Lazarus 指南
本文写给了解 Delphi 并对 Lazarus 感兴趣的人,介绍了两者的差异。
Delphi -> Lazarus
Lazarus 是类似 Delphi 的快速应用程序开发(RAD) 工具。这表示它带有可视化组件库和集成开发环境(IDE)。Lazarus 组件库(LCL)与 Delphi 的可视化组件库 (VCL)非常相像。大多数 Lazarus 单元、类和属性具备与 Delphi 相同的名称和功能。于是将 Delphi 应用程序移植到 Lazarus 相对容易。尽管从很多方面看 Lazarus 都是 Delphi 的开源版本,但两者并非 100% 兼容。
Lazarus 和 Delphi 的最大区别
- Lazarus 完全开源
- Lazarus 以平台无关方式编写
- Lazarus 使用 Free Pascal 编译器(FPC)
可运行 FPC 的平台已超过 15 个(参见 平台列表)。但并非所有 FPC 软件包和程序库都已移植到全部 15 个平台上了,因此目前 Lazarus 可运行于:
- Linux (i386, x86_64)
- FreeBSD (i386, x86_64)
- macOS (PowerPC, i386, x86_64, ARM64)
- Windows (i386, x86_64)
转换 Delphi 项目为 Lazarus 项目时的首要工作
打开 Lazarus 的工具菜单,然后打开转换 Delphi 项目为 Lazarus 项目(从 Lazarus 1.2.6 开始,菜单结构是工具 -> Delphi 转换器 -> 转换 Delphi 项目为 Lazarus 项目)。 该功能虽然无法实现全部的转换,但仍会减少很多工作量。请注意,Lazarus IDE 中的转换工具通常只做单向转换。如果需要保持 Delphi 兼容性(Delphi 和 Lazarus 均可编译项目),可以考虑换用 XDev Toolkit 进行转换。
对 Unicode 的支持
Delphi 2007 及以下版本均不支持 Unicode,而是使用 Windows ANSI 编码。自 Delphi 2009 开始,通过 UTF-16 编码的字符串支持 Unicode 了。
而 Lazarus 很早就开始支持 Unicode 了,并采用 UTF-8 编码字符串。详情请参阅 LCL 对 Unicode 的 支持。
Delphi IDE -> Lazarus IDE
项目
Delphi 应用程序的主文件是 .dpr 文件。Delphi 中的 .dpr 文件既是程序的主源代码,也是 Delphi IDE 保存相关编译器开关和单元文件位置信息的文件。
Lazarus 应用程序也有对应的 .lpr 文件,也是项目的Pascal 源代码主文件。但 Lazarus 项目的主文件是 .lpi 文件(Lazarus 项目信息),与 .lpr 文件一同创建。所有项目相关的信息(例如编译器开关、用到的单元文件路径等)都存放于 .lpi 文件中。因此 .lpi 是个重要文件。在大多数平台中,在文件资源管理器中双击 .lpi 文件将会启动 Lazarus IDE 并打开项目。
举个例子:
比如 Delphi 将项目单元文件路径存于 .dpr 文件,如下所示:
unit1 in 'path/Unit1.pas';
这里的 in 是 Delphi 的特有语法,Lazarus IDE 不会读取。请勿继续使用。Lazarus 会在项目选项对话框中(项目->项目选项...),用编译器选项下的路径页设置项目所在目录之外的单元文件路径。请注意,在添加软件包依赖项时,IDE 都会自动设置大多数单元文件的路径。例如,所有标准 Lazarus LCL 项目都会默认将 LCL 添加为依赖项,因此新 Lazarus LCL 项目(项目->新建项目->应用程序)都知晓所有 LCL 已编译单元文件的路径,无需再做什么操作。
Delphi 将编译器选项存于 .dpr 文件中,如 {$APPTYPE CONSOLE}。Lazarus IDE 会忽略这些选项。请勿继续使用。请改用项目选项 对话框中的 编译器选项 页。
Lazarus IDE 没有“空白”项目
Lazarus IDE 有一个重要规则:始终会有一个项目打开着。 “关闭”项目的唯一方法就是退出 Lazarus 或打开另一个项目。这是因为 Lazarus 项目同时也是一个“会话”。因此,会话信息(例如当前的编辑器设置)也存储在.lpi文件中,后续重新打开项目时,Lazarus 编辑器会恢复至最后保存的状态。 假设正在调试应用程序,设置了很多断点和书签。这时 Lazarus 可以随时保存项目、关闭整个程序或打开另一个项目。重新打开此项目(甚至可能是在另一台计算机上),所有断点、书签、打开的文件、光标位置、跳转历史等都会恢复。
源码编辑器
几乎所有的按键和快捷键都可以在“工具”->“选项”->“编辑器”->“键盘映射”中定义。
Lazarus IDE 提供了许多源代码工具。其中很多工具的外观和工作方式都与 Delphi 非常相像。但有一点重要的差别: Lazarus 不通过编译器读取代码信息,而是直接解析源代码。这具有许多重要的优势:
源码编辑器能够让“注释”成为一种功能。在 Delphi 中,源码注释只是代码之间的空白符号。注释没有任何影响代码的功能,自动插入代码时,注释位置就会发生移动。而在 Lazarus 中,甚至可在注释内的代码中查找声明。尽管并不完全可靠,但一般都会有效。在插入新的代码时,IDE 会启发式地将注释和代码放在一起。比如:“c: char; // comment”不会被拆成两行。
Delphi 的“代码补全”(Ctrl+Space)功能在 Lazarus 中称为“标识符补全”。Lazarus 的“代码补全”功能,整合了“类自动补全”(与 Delphi 一样)、“局部变量补全”和“事件补全”。这些功能都通过 Ctrl+Shift+C 调用,IDE 会根据光标位置确定具体调用哪一个。
局部变量补全的示例
假定新建了一个方法,并编写了语句 “i:=3;”。
procedure TForm1.DoSomething;
begin
i := 3;
end;
光标置于 "i" 之上,按下 Ctrl+⇧ Shift+C 即可:
procedure TForm1.DoSomething;
var i: Integer;
begin
i := 3;
end;
事件代码补全示例
对象查看器有一个很好用的功能:自动创建方法。源码编辑器也可以自动创建事件。
例如:
Button1.OnClick:=
光标放在赋值操作符 ":=" 之后,按下 Ctrl+⇧ Shift+C 即可:
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.OnClick:=@Button1Click;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
end;
过程调用补全示例
假定编写了语句 "DoSomething(Width);" :
procedure SomeProcedure;
var
Width: integer;
begin
Width:=3;
DoSomething(Width);
end;
将光标置于标识符 "DoSomething" 之上,按下 Ctrl+⇧ Shift+C 即可:
procedure DoSomething(aWidth: LongInt);
begin
end;
procedure SomeProcedure;
var
Width: integer;
begin
Width:=3;
DoSomething(Width);
end;
"代码补全" Ctrl+W
类似于“标识符补全”,但范围不是 Pascal 标识符,而是所有单词。可从所有当前打开的文件中选择开头字母相同的所有单词。
包含文件的支持
Delphi 不支持包含文件,因此您可能还未创建太多的包含文件。但包含文件有一个很大的优势:不必让 IFDEF 弄乱代码,就能编写平台依赖(或无关)的代码。
比如:方法跳转、类补全、查找声明……这些功能都适用于包含文件。
代码编辑功能带有很多选项。
设计器
- Guidelines
对象查看器
在 Delphi 和 Lazarus IDE 中,可用对象查看器进行组件属性编辑、事件赋予等操作。以下是一些细微的用法差别:
- 自 Delphi 5 开始,除了传统的下拉列表之外,还引入了 Object Treeview,可在对象查看器中使用树状视图,在层次结构中遍历和选择对象。在 Lazarus 中,树状视图包含于对象查看器之中,替代了默认的下拉列表。通过右键菜单“显示组件树” 可以选择是否启用。
- 在 Delphi 中,双击空白事件会自动创建其处理程序,源码编辑器会定位至对应位置。而在 Lazarus 中,此功能通过选定下拉列表右侧的一个按钮来实现。
- 在 Delphi 中,若要移除事件的关联,需手动删除事件名称。而在 Lazarus 中,可通过下拉列表选择 "(None)" 来取消事件的关联。
- 与事件的处理方式类似,Lazarus 中双击常规属性(如布尔值)不会修改值,必须在下拉列表中选择。若要用编辑器窗口打开已关联的属性,必须单击输入框或下拉列表右侧的“...”按钮。
软件包
Lazarus 能安装并使用 Delphi 软件包吗?
- 不能,因为需要用到 Delphi 编译器。
需要为 Lazarus 重新制作吗?
- 是的。
- 新建一个软件包,存入源码所在目录(通常是 .dpk 文件所在目录),必须添加 LCL,最后加入全部 .pas 文件。现在可以安装或在项目中使用了。有些 Lazarus 和 Delphi 软件包是存在差异的。
VCL -> LCL
VCL -> LCL
尽管 VCL(Visual Component Library)和 LCL(Lazarus Component Library)的服务目标非常相像——尤其适用于快速应用开发的面向对象组件架构,但两者并不完全相同。比如,VCL 提供了很多非可视化组件,而 LCL 则尽量只提供可视化的组件,而大多数非可视化组件(如数据库访问)则由 Free Pascal 的 FCL 提供。
此外,很多 VCL 控件可能在 LCL 中没有,反之亦然;即便是两边都有的控件,也并非完全相同,在移植应用程序、组件和控件时必须做出修改。
下面尽量全面地为 Delphi 用户介绍两者的主要差异或不兼容之处。主要涵盖当前LCL(含 CVS 版本)与 D4 版本 VCL 的差异,有时也会涉及D5、D6 或 D7。因此,可能并不一定精确匹配您用的 Delphi 或 LCL 版本。若发现与 CVS 版 LCL 或您的 Delphi 版本不符,请尽管添加和修改内容,以保持本页内容的全面性,让所有人受益。
TControl.Font/TControl.ParentFont
VCL 有个操作非常常见,即为各个控件的字体和字体属性(如粗体和斜体)设置不同的值,并且这些设置一直会保留下去。另外 VCL 还提供了 TControl.ParentFont 属性,确保控件一直沿用父控件的字体。再次强调一下,这时隐含假定了,无论 Windows 如何设置,这些值都会保持不变。
但在 LCL 中却并非总是如此,也做不到。LCL 具备跨平台/跨界面特性,因此更倾向于采取一种平衡的方法,始终尝试让控件采用原生桌面/工具外观或主题设置。例如,若调用 GTK 接口且其主题为按钮设置了字体,那么 LCL 按钮一定会尝试使用该字体。
这意味着大多数 LCL 控件不具备 VCL 常见的这种控制能力,而只有那些使用 Canvas 绘制而不是调用接口的自定义控件,才能无视接口维持字体的沿袭不变。
控件拖动与停靠
在 VCL 中,大多数(Windows)控件实现了拖放、停靠的方法和回调函数,比如在运行时可将控件从一个面板拖离并停靠到另一个面板上。
这种功能目前在 LCL 中尚未实现或未完全实现,不过已处于规划的起步阶段,最终应该会提供某种程度的支持,即使方式不会完全相同。
这意味着目前尚无控件可继承或使用 TControl 的以下函数、过程、属性或事件:
Protected
function GetDockEdge(MousePos: TPoint): TAlign;
function GetDragImages: TDragImageList;
function GetFloating: Boolean;
function GetFloatingDockSiteClass: TWinControlClass;
procedure DoEndDrag(Target:TObject); X, Y: Integer);
procedure DockTrackNoTarget(Source: TDragDockObject; X, Y: Integer);
procedure DoEndDock(Target: TObject; X, Y: Integer);
procedure DoDock(NewDockSite: TWinControl; var ARect: TRect);
procedure DoStartDock(var DragObject: TDragObject);
procedure DragCanceled;
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;
var Accept: Boolean);
procedure DoEndDrag(Target: TObject; X, Y: Integer);
procedure DoStartDrag(var DragObject: TDragObject);
procedure DrawDragDockImage(DragDockObject: TDragDockObject);
procedure EraseDragDockImage(DragDockObject: TDragDockObject);
procedure PositionDockRect(DragDockObject: TDragDockObject);
procedure SetDragMode(Value: TDragMode);
property DragKind: TDragKind;
property DragCursor: TCursor;
property DragMode: TDragMode;
property OnDragDrop: TDragDropEvent;
property OnDragOver: TDragOverEvent;
property OnEndDock: TEndDragEvent;
property OnEndDrag: TEndDragEvent;
property OnStartDock: TStartDockEvent;
property OnStartDrag: TStartDragEvent;
public
function Dragging: Boolean;
function ManualDock(NewDockSite: TWinControl; DropControl: TControl;
ControlSide: TAlign): Boolean;
function ManualFloat(ScreenPos: TRect): Boolean;
function ReplaceDockedControl(Control: TControl; NewDockSite: TWinControl;
DropControl: TControl; ControlSide: TAlign): Boolean;
procedure BeginDrag(Immediate: Boolean; Threshold: Integer);
procedure Dock(NewDockSite: TWinControl; ARect: TRect);
procedure DragDrop(Source: TObject; X, Y: Integer);
procedure EndDrag(Drop: Boolean);
property DockOrientation: TDockOrientation;
property Floating: Boolean;
property FloatingDockSiteClass: TWinControlClass;
property HostDockSite: TWinControl;
property LRDockWidth: Integer;
property TBDockHeight: Integer;
property UndockHeight: Integer;
property UndockWidth: Integer;
以下类不存在或不可用:
TDragImageList = class(TCustomImageList)
TDockZone = class
TDockTree = class(TInterfacedObject, IDockManager)
TDragObject = class(TObject)
TBaseDragControlObject = class(TDragObject)
TDragControlObject = class(TBaseDragControlObject)
TDragDockObject = class(TBaseDragControlObject)
以下函数也不可用或不兼容:
function FindDragTarget(const Pos: TPoint;
AllowDisabled: Boolean) : TControl;
procedure CancelDrag;
function IsDragObject(sender: TObject): Boolean;
关于停靠管理的初步知识,请参阅锚点停靠
TEdit/TCustomEdit
LCL 的编辑控件虽然在功能上与 VCL 基本相同,但在代码转换时还是有一些问题需要注意:
- 由于界面接口的限制,TEdit.PasswordChar 并非都能正常工作(将来可能会有所改善),在需要隐藏文本时应采用将 TCustomEdit.EchoMode 设为 emPassword 的方式。
- Drag/Dock 事件处理程序尚未实现。详见前文 控件拖动与停靠
- 为保持接口的一致性,Font 属性通常会被忽略。详细原因请参阅 TControl.Font/TControl.ParentFont
TDBImage
Delphi 和 Lazarus 都提供了 TDBImage 控件,用于显示存于数据库字段中的图片。在当前稳定版本(1.2.4)中,Lazarus 会将数据库字段中的图片类别信息存于实际的图片数据之前。请参见 TDBImage.UpdateData 过程的代码。
这意味着 Delphi 和旧版 Lazarus 不兼容。
目前的 Lazarus 稳定版本(1.2.0+)已允许保持与 Delphi 的兼容性。有关如何启用的详细信息,请参阅Lazarus_1.2.0_release_notes#TDBImage。
目前的 Lazarus 主版本在将 Delphi 格式的数据库字段读入 TDBImage 时,会回退至与 Delphi 兼容的处理方式。
(可选的)TSplitter -> TPairSplitter
请大家在此完善内容
目前 LCL 中有一个 TSplitter 控件,因此不需要转换代码。
不过下面会作解释:
以下内容大致源于邮件列表 Vincent Snijders 的提问和 Andrew Johnson 的答复:
在 VCL 中,“分隔”控件由 TSplitter 实现,即可供在两个组件之间拖动的控制柄,用于调节两个组件占用空间的比例。这种控件很常见,比如在 Delphi IDE 中,已停靠的 Code Explorer 和 Source Viewer 之间就有一个。
LCL提供了自己的控件 TPairSplitter,用途相同但不兼容;因此在移植代码时,需对残缺的 VCL 或 Delphi DFM 代码进行“修正”,当然两者有很多相同之处。
- 那么到底有哪些不同点呢?
最大的区别是 VCL TSplitter 没有子控件,它只是置于两个对齐的控件之间,以便在运行时调整两边的尺寸,而不考虑自己的尺寸。它的两侧必须有两个对齐的控件。举个简单的例子,假设有一个窗体包含一个左对齐的 Panel、一个左对齐的 Splitter 和一个填充对齐(Client)的 Panel。在运行时,拖动 Splitter 控件的控制柄可以重新调整每个 Panel 的大小。
而 LCL 中的 TPairSplitter 是一种特殊的控件,它带有两个 Panel,只有当两个 Panel 上放有需分隔的控件时才有效果;但无论 Panel 上是否有其他控件,仍会对 Panel 进行分隔。因此针对上述例子,将会是窗体包含一个填充对齐的 TPairSplitter,其左右两侧各有一个填充对齐的 Panel。
另一个重要区别是,VCL 的 TSplitter 由于本身就是 TControl,在调整大小时相对其他控件的位置会保持不变。于是当一个填充对齐的 Panel 增大而其他 Panel 不变时,分隔条的位置将与被分隔的控件对齐。
在 LCL 中,由于两侧 Panel 是分开的,因此 TPairSplitter 带有一个相对左、上侧的 Position 属性。在调整大小时,实际位置不会随内部控件而改动,如果调整大小时保持相对比例很重要,就必须设置回调函数。
举个例子,如果纵向分隔条的右侧需要 alClient 对齐模式,则需添加一个窗体大小变动的回调函数,执行类似如下操作:
PairSplitter.Position := PairSplitter.Width - PairSplitter.Position;
- 如何将使用 TSplitter 的代码转换为 TPairSplitter?
如果分隔条和控件是在函数(如窗体 OnCreate 时)中创建的,那么转换应该不会很困难,主要工作是重新组织代码,按照新的控件结构创建控件,并将要分隔的子控件的父控件设为 PairSplitter 的左/顶和右/底。以下是个例子:
VCL | LCL |
var
BottomPanel: TPanel;
VerticalSplitter: TSplitter;
LeftPanel: TPanel;
HorizontalSplitter: TSplitter;
MainPanel: TPanel;
begin
BottomPanel:= TPanel.Create(Self);
with (BottomPanel) do
begin
Parent:= Self;
Height:= 75;
Align:= alBottom;
end;
VerticalSplitter:= TSplitter.Create(Self);
with (VerticalSplitter) do
begin
Parent:= Self;
Align:= alBottom;
end;
HorizontalSplitter:= TSplitter.Create(Self);
with (HorizontalSplitter) do
begin
Parent:= Self;
align:= alLeft;
end;
LeftPanel:= TPanel.Create(Self);
with (LeftPanel) do
begin
Parent:= Self;
Width:= 125;
Align:= alLeft;
end;
MainPanel:= TPanel.Create(Self);
with (MainPanel) do
begin
Parent:= Self;
Align:= alClient;
Caption:= 'Hello';
end;
end;
|
var
BottomPanel: TPanel;
VerticalSplitter: TPairSplitter;
LeftPanel: TPanel;
HorizontalSplitter: TPairSplitter;
MainPanel: TPanel;
begin
VerticalSplitter:= TPairSplitter.Create(Self);
with (VerticalSplitter) do
begin
Parent:= Self;
Align:= alClient;
Width:= Self.Width;
Height:= Self.Height;
SplitterType:= pstVertical;
Position:= Height - 75;
Sides[0].Width:= Width;
Sides[0].Height:= Position;
end;
HorizontalSplitter:= TPairSplitter.Create(Self);
with (HorizontalSplitter) do
begin
Parent:= VerticalSplitter.Sides[0];
Width:= Self.Width;
Height:= VerticalSplitter.Position;
align:= alClient;
SplitterType:= pstHorizontal;
Position:= 125;
end;
LeftPanel:= TPanel.Create(Self);
with (LeftPanel) do
begin
Parent:= HorizontalSplitter.Sides[0];
Align:= alClient;
end;
MainPanel:= TPanel.Create(Self);
with (MainPanel) do
begin
Parent:= HorizontalSplitter.Sides[1];
Align:= alClient;
Caption:= 'Hello';
end;
BottomPanel:= TPanel.Create(Self);
with (BottomPanel) do
begin
Parent:= VerticalSplitter.Sides[1];
Align:= alClient;
end;
end;
|
这与大多数控件层次结构相当一致。如果熟悉 DFM,以上已充分说明了 DFM->LFM 所需的改动,就是对 Parent/Owner 等属性进行同类修改。
于是上面的例子应该类似如下:
Delphi DFM (extraneous values removed)
|
Lazarus LFM (most width, height, etc. removed)
|
object VerticalSplitter: TSplitter
Height = 3
Cursor = crVSplit
Align = alBottom
end
object HorizontalSplitter: TSplitter
Width = 3
Align = alLeft
end
object BottomPanel: TPanel
Height = 75
Align = alBottom
end
object LeftPanel: TPanel
Width = 125
Align = alLeft
end
object MainPanel: TPanel
Align = alClient
end
|
object VerticalSplitter: TPairSplitter
Align = alClient
SplitterType = pstVertical
Position = 225
Height = 300
Width = 400
object Pairsplitterside1: TPairSplitterIde
object HorizontalSplitter: TPairSplitter
Align = alClient
Position = 125
object Pairsplitterside3: TPairSplitterIde
Width = 125
object LeftPanel: TPanel
Align = alClient
Width = 125
end
end
object Pairsplitterside4: TPairSplitterIde
object MainPanel: TPanel
Align = alClient
end
end
end
end
object Pairsplitterside2: TPairSplitterIde
object BottomPanel: TPanel
Align = alClient
Height = 75
end
end
end
|
TCustomTreeView/TTreeView
VCL 和 LCL 都提供了 TCustomTreeView/TTreeView 组件,用于显示多节点的树形结构,可勾选及显示图片。尽管实际功能差不多,但并非所有属性都相互兼容。主要区别如下:
内容并不完整,有待更新 TCustomTreeView 的 Mark 函数和受保护的方法
- LCL 提供 TCustomTreeView.Options 属性,可以设置控件属性,修改外观和功能。包括:
- tvoAllowMultiselect - 启用多选模式,等效于 D6 VCL 中启用 TCustomTreeView.MultiSelect
- tvoAutoExpand - 节点自动展开,等效于启用 TCustomTreeView.AutoExpand
- tvoAutoInsertMark - 鼠标拖动时更新预览
- tvoAutoItemHeight - 自动调整高度
- tvoHideSelection - 不标记选中项
- tvoHotTrack - 启用 Hot Track,等效于启用 TCustomTreeview.HotTrack
- tvoKeepCollapsedNodes - 节点折叠后,留存子节点
- tvoReadOnly - Treeview 只读模式,等效于启用 TCustomTreeview.ReadOnly
- tvoRightClickSelect - 允许选中节点能用鼠标右键点击,等效于启用 TCustomTreeView.RightClickSelect
- tvoRowSelect - 允许选中行,等效于启用 TCustomTreeView.RowSelect
- tvoShowButtons - 显示按钮,等效于启用 TCustomTreeView.ShowButtons
- tvoShowLines - 显示树的竖线,等效于启用 TCustomTreeView.ShowLines
- tvoShowRoot - 显式根节点,等效于启用 TCustomTreeView.ShowRoot
- tvoShowSeparators - 显示树的横线分隔符
- tvoToolTips - 为过长节点显示提示信息
- LCL 提供更多属性:
- TCustomTreeView.OnSelectionChange 事件
- TCustomTreeView.DefaultItems,默认节点数(文档中未找到)
- TCustomTreeView.ExpandSignType,用于确定展开/折叠图标
- 尽管 LCL 控件中给出了大部分拖动/停靠事件,但并不起作用。详情请参阅前文控件拖动/停靠部分。
消息 / 事件
LCL 的消息和事件(如 OnShow、OnActivate、OnEnter 等)处理顺序和频率与 VCL 不同,且依赖于各平台的可视化组件。
LCL 提供了一组类似 WinAPI 的消息,以简化 Delphi 组件的移植,但几乎所有 LCL 消息的工作方式都与对应的 VCL/WinAPI 略有不同。Delphi 代码之所以要用到 WinAPI 消息,大多数是因为 VCL 缺少某些功能或为了提高性能。在 LCL 中不太会这么干,因此必须进行人工检查。比如 LCL 消息称为 LM_SIZE 而不是 WM_SIZE(lmessages 单元中),就是这个原因。
处理自定义消息时的注意事项!
LCL 自 0.9.26 版本(2008年12月)开始,自定义 WinApi 消息(如 WM_HOTKEY、WM_SYSCOMMAND)的处理方式与 Delphi 不一样了。现在无法通过 message 指令或重写窗体 WndProc 方法进行处理了。在窗体中处理自定义消息的唯一方案,就是自行挂钩 Windows 的 windowproc。详情请阅读: 在窗体中处理非用户消息。