Sign in Join
Wiki copytemporary

Lazarus For Delphi Users/zh CN: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Lazarus For Delphi Users/zh CN

OldNewLine
1+Delphi 用户的 Lazarus 指南
2+
3+本文写给了解 Delphi 并对 Lazarus 感兴趣的人,介绍了两者的差异。
4+
5+## Delphi -> Lazarus
6+
7+Lazarus 是类似 Delphi 的快速应用程序开发(RAD) 工具。这表示它带有可视化组件库和集成开发环境(IDE)。Lazarus 组件库(LCL)与 Delphi 的可视化组件库 (VCL)非常相像。大多数 Lazarus 单元、类和属性具备与 Delphi 相同的名称和功能。于是将 Delphi 应用程序移植到 Lazarus 相对容易。尽管从很多方面看 Lazarus 都是 Delphi 的开源版本,但两者并非 100% 兼容。
8+
9+### Lazarus 和 Delphi 的最大区别
10+
11+ * Lazarus 完全开源
12+ * Lazarus 以平台无关方式编写
13+ * Lazarus 使用 [[Free Pascal]] 编译器(FPC)
14+
15+可运行 FPC 的平台已超过 15 个(参见 [[Platform list|平台列表]])。但并非所有 FPC 软件包和程序库都已移植到全部 15 个平台上了,因此目前 Lazarus 可运行于:
16+
17+ * Linux (i386, x86_64)
18+ * FreeBSD (i386, x86_64)
19+ * macOS (PowerPC, i386, x86_64, ARM64)
20+ * Windows (i386, x86_64)
21+
22+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** Lazarus 尚在发展,本文也是。我们一直在寻求新的开发人员、软件包开发人员、移植人员、文档编辑……
23+
24+### 转换 Delphi 项目为 Lazarus 项目时的首要工作
25+
26+打开 Lazarus 的**工具** 菜单,然后打开**转换 Delphi 项目为 Lazarus 项目** (从 Lazarus 1.2.6 开始,菜单结构是**工具** -> **Delphi 转换器** -> **转换 Delphi 项目为 Lazarus 项目** )。 该功能虽然无法实现全部的转换,但仍会减少很多工作量。请注意,Lazarus IDE 中的转换工具通常只做单向转换。如果需要保持 Delphi 兼容性(Delphi 和 Lazarus 均可编译项目),可以考虑换用 [[XDev Toolkit]] 进行转换。
27+
28+### 对 Unicode 的支持
29+
30+Delphi 2007 及以下版本均不支持 Unicode,而是使用 Windows ANSI 编码。自 Delphi 2009 开始,通过 UTF-16 编码的字符串支持 Unicode 了。
31+
32+而 Lazarus 很早就开始支持 Unicode 了,并采用 UTF-8 编码字符串。详情请参阅 [[LCL Unicode Support|LCL 对 Unicode 的 支持]]。
33+
34+## Delphi IDE -> Lazarus IDE
35+
36+### 项目
37+
38+Delphi 应用程序的主文件是 .dpr 文件。Delphi 中的 .dpr 文件既是程序的主源代码,也是 Delphi IDE 保存相关编译器开关和单元文件位置信息的文件。
39+
40+Lazarus 应用程序也有对应的 .lpr 文件,也是项目的**Pascal 源代码** 主文件。但 Lazarus 项目的主文件是 .lpi 文件(Lazarus 项目信息),与 .lpr 文件一同创建。所有项目相关的信息(例如编译器开关、用到的单元文件路径等)都存放于 .lpi 文件中。因此 .lpi 是个重要文件。在大多数平台中,在文件资源管理器中双击 .lpi 文件将会启动 Lazarus IDE 并打开项目。
41+
42+举个例子:
43+
44+比如 Delphi 将项目单元文件路径存于 .dpr 文件,如下所示:
45+```
46+unit1 in 'path/Unit1.pas';
47+```
48+
49+这里的 in 是 Delphi 的特有语法,Lazarus IDE 不会读取。请勿继续使用。Lazarus 会在**项目选项** 对话框中(项目->项目选项...),用**编译器选项** 下的**路径** 页设置项目所在目录之外的单元文件路径。请注意,在添加软件包依赖项时,IDE 都会自动设置大多数单元文件的路径。例如,所有标准 Lazarus LCL 项目都会默认将 LCL 添加为依赖项,因此新 Lazarus LCL 项目(项目->新建项目->应用程序)都知晓所有 LCL 已编译单元文件的路径,无需再做什么操作。
50+
51+Delphi 将编译器选项存于 .dpr 文件中,如 {$APPTYPE CONSOLE}。Lazarus IDE 会忽略这些选项。请勿继续使用。请改用**项目选项** 对话框中的 **编译器选项** 页。
52+
53+### Lazarus IDE 没有“空白”项目
54+
55+Lazarus IDE 有一个重要规则: _始终_ 会有一个项目打开着。 “关闭”项目的唯一方法就是退出 Lazarus 或打开另一个项目。这是因为 Lazarus 项目同时也是一个“会话”。因此,会话信息(例如当前的编辑器设置)也存储在.lpi文件中,后续重新打开项目时,Lazarus 编辑器会恢复至最后保存的状态。 假设正在调试应用程序,设置了很多断点和书签。这时 Lazarus 可以随时保存项目、关闭整个程序或打开另一个项目。重新打开此项目(甚至可能是在另一台计算机上),所有断点、书签、打开的文件、光标位置、跳转历史等都会恢复。
56+
57+### 源码编辑器
58+
59+几乎所有的按键和快捷键都可以在“工具”->“选项”->“编辑器”->“键盘映射”中定义。
60+
61+Lazarus IDE 提供了许多源代码工具。其中很多工具的外观和工作方式都与 Delphi 非常相像。但有一点重要的差别: Lazarus 不通过编译器读取代码信息,而是直接解析源代码。这具有许多重要的优势:
62+
63+源码编辑器能够让“注释”成为一种功能。在 Delphi 中,源码注释只是代码之间的空白符号。注释没有任何影响代码的功能,自动插入代码时,注释位置就会发生移动。而在 Lazarus 中,甚至可在注释内的代码中查找声明。尽管并不完全可靠,但一般都会有效。在插入新的代码时,IDE 会启发式地将注释和代码放在一起。比如:“c: char; // comment”不会被拆成两行。
64+
65+Delphi 的“代码补全”(Ctrl+Space)功能在 Lazarus 中称为“标识符补全”。Lazarus 的“代码补全”功能,整合了“类自动补全”(与 Delphi 一样)、“局部变量补全”和“事件补全”。这些功能都通过 Ctrl+Shift+C 调用,IDE 会根据光标位置确定具体调用哪一个。
66+
67+#### 局部变量补全的示例
68+
69+假定新建了一个方法,并编写了语句 “i:=3;”。
70+```
71+procedure TForm1.DoSomething;
72+ begin
73+ i := 3;
74+ end;
75+```
76+
77+光标置于 "i" 之上,按下 `Ctrl`+`⇧ Shift`+`C` 即可:
78+```
79+procedure TForm1.DoSomething;
80+ var i: Integer;
81+ begin
82+ i := 3;
83+ end;
84+```
85+
86+#### 事件代码补全示例
87+
88+对象查看器有一个很好用的功能:自动创建方法。源码编辑器也可以自动创建事件。
89+
90+例如:
91+```
92+Button1.OnClick:=
93+```
94+
95+光标放在赋值操作符 ":=" 之后,按下 `Ctrl`+`⇧ Shift`+`C` 即可:
96+```
97+procedure TForm1.FormCreate(Sender: TObject);
98+ begin
99+ Button1.OnClick:=@Button1Click;
100+ end;
101+
102+ procedure TForm1.Button1Click(Sender: TObject);
103+ begin
104+
105+ end;
106+```
107+
108+#### 过程调用补全示例
109+
110+假定编写了语句 "DoSomething(Width);" :
111+```
112+procedure SomeProcedure;
113+ var
114+ Width: integer;
115+ begin
116+ Width:=3;
117+ DoSomething(Width);
118+ end;
119+```
120+
121+将光标置于标识符 "DoSomething" 之上,按下 `Ctrl`+`⇧ Shift`+`C` 即可:
122+```
123+procedure DoSomething(aWidth: LongInt);
124+ begin
125+
126+ end;
127+
128+ procedure SomeProcedure;
129+ var
130+ Width: integer;
131+ begin
132+ Width:=3;
133+ DoSomething(Width);
134+ end;
135+```
136+
137+#### "代码补全" Ctrl+W
138+
139+类似于“标识符补全”,但范围不是 Pascal 标识符,而是所有单词。可从所有当前打开的文件中选择开头字母相同的所有单词。
140+
141+#### 包含文件的支持
142+
143+Delphi 不支持包含文件,因此您可能还未创建太多的包含文件。但包含文件有一个很大的优势:不必让 IFDEF 弄乱代码,就能编写平台依赖(或无关)的代码。
144+
145+比如:方法跳转、类补全、查找声明……这些功能都适用于包含文件。
146+
147+代码编辑功能带有很多选项。
148+
149+### 设计器
150+
151+\- Guidelines
152+
153+#### 对象查看器
154+
155+在 Delphi 和 Lazarus IDE 中,可用对象查看器进行组件属性编辑、事件赋予等操作。以下是一些细微的用法差别:
156+
157+ 1. 自 Delphi 5 开始,除了传统的下拉列表之外,还引入了 Object Treeview,可在对象查看器中使用树状视图,在层次结构中遍历和选择对象。在 Lazarus 中,树状视图包含于对象查看器之中,替代了默认的下拉列表。通过右键菜单“显示组件树” 可以选择是否启用。
158+
159+ 1. 在 Delphi 中,双击空白事件会自动创建其处理程序,源码编辑器会定位至对应位置。而在 Lazarus 中,此功能通过选定下拉列表右侧的一个按钮来实现。
160+
161+ 1. 在 Delphi 中,若要移除事件的关联,需手动删除事件名称。而在 Lazarus 中,可通过下拉列表选择 "(None)" 来取消事件的关联。
162+
163+ 1. 与事件的处理方式类似,Lazarus 中双击常规属性(如布尔值)不会修改值,必须在下拉列表中选择。若要用编辑器窗口打开已关联的属性,必须单击输入框或下拉列表右侧的“...”按钮。
164+
165+#### 软件包
166+
167+Lazarus 能安装并使用 Delphi 软件包吗?
168+
169+不能,因为需要用到 Delphi 编译器。
170+
171+需要为 Lazarus 重新制作吗?
172+
173+是的。
174+
175+新建一个软件包,存入源码所在目录(通常是 .dpk 文件所在目录),必须添加 LCL,最后加入全部 .pas 文件。现在可以安装或在项目中使用了。有些 [[packages|Lazarus 和 Delphi 软件包是存在差异的]]。
176+
177+## VCL -> LCL
178+
179+## VCL -> LCL
180+
181+尽管 VCL(Visual Component Library)和 LCL(Lazarus Component Library)的服务目标非常相像——尤其适用于快速应用开发的面向对象组件架构,但两者并不完全相同。比如,VCL 提供了很多非可视化组件,而 LCL 则尽量只提供可视化的组件,而大多数非可视化组件(如数据库访问)则由 [[Free Pascal]] 的 FCL 提供。
182+
183+此外,很多 VCL 控件可能在 LCL 中没有,反之亦然;即便是两边都有的控件,也并非完全相同,在移植应用程序、组件和控件时必须做出修改。
184+
185+下面尽量全面地为 Delphi 用户介绍两者的主要差异或不兼容之处。主要涵盖当前LCL(含 CVS 版本)与 D4 版本 VCL 的差异,有时也会涉及D5、D6 或 D7。因此,可能并不一定精确匹配您用的 Delphi 或 LCL 版本。若发现与 CVS 版 LCL 或您的 Delphi 版本不符,请尽管添加和修改内容,以保持本页内容的全面性,让所有人受益。
186+
187+### TControl.Font/TControl.ParentFont
188+
189+VCL 有个操作非常常见,即为各个控件的字体和字体属性(如粗体和斜体)设置不同的值,并且这些设置一直会保留下去。另外 VCL 还提供了 TControl.ParentFont 属性,确保控件一直沿用父控件的字体。再次强调一下,这时隐含假定了,无论 Windows 如何设置,这些值都会保持不变。
190+
191+但在 LCL 中却并非总是如此,也做不到。LCL 具备跨平台/跨界面特性,因此更倾向于采取一种平衡的方法,始终尝试让控件采用原生桌面/工具外观或主题设置。例如,若调用 GTK 接口且其主题为按钮设置了字体,那么 LCL 按钮一定会尝试使用该字体。
192+
193+这意味着大多数 LCL 控件不具备 VCL 常见的这种控制能力,而只有那些使用 Canvas 绘制而不是调用接口的自定义控件,才能无视接口维持字体的沿袭不变。
194+
195+### 控件拖动与停靠
196+
197+在 VCL 中,大多数(Windows)控件实现了拖放、停靠的方法和[[callback|回调]]函数,比如在运行时可将控件从一个面板拖离并停靠到另一个面板上。
198+
199+这种功能目前在 LCL 中尚未实现或未完全实现,不过已处于规划的起步阶段,最终应该会提供某种程度的支持,即使方式不会完全相同。
200+
201+这意味着目前尚无控件可继承或使用 TControl 的以下函数、过程、属性或事件:
202+```
203+Protected
204+ function GetDockEdge(MousePos: TPoint): TAlign;
205+ function GetDragImages: TDragImageList;
206+ function GetFloating: Boolean;
207+ function GetFloatingDockSiteClass: TWinControlClass;
208+ procedure DoEndDrag(Target:TObject); X, Y: Integer);
209+ procedure DockTrackNoTarget(Source: TDragDockObject; X, Y: Integer);
210+ procedure DoEndDock(Target: TObject; X, Y: Integer);
211+ procedure DoDock(NewDockSite: TWinControl; var ARect: TRect);
212+ procedure DoStartDock(var DragObject: TDragObject);
213+ procedure DragCanceled;
214+ procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;
215+ var Accept: Boolean);
216+ procedure DoEndDrag(Target: TObject; X, Y: Integer);
217+ procedure DoStartDrag(var DragObject: TDragObject);
218+ procedure DrawDragDockImage(DragDockObject: TDragDockObject);
219+ procedure EraseDragDockImage(DragDockObject: TDragDockObject);
220+ procedure PositionDockRect(DragDockObject: TDragDockObject);
221+ procedure SetDragMode(Value: TDragMode);
222+ property DragKind: TDragKind;
223+ property DragCursor: TCursor;
224+ property DragMode: TDragMode;
225+ property OnDragDrop: TDragDropEvent;
226+ property OnDragOver: TDragOverEvent;
227+ property OnEndDock: TEndDragEvent;
228+ property OnEndDrag: TEndDragEvent;
229+ property OnStartDock: TStartDockEvent;
230+ property OnStartDrag: TStartDragEvent;
231+ public
232+ function Dragging: Boolean;
233+ function ManualDock(NewDockSite: TWinControl; DropControl: TControl;
234+ ControlSide: TAlign): Boolean;
235+ function ManualFloat(ScreenPos: TRect): Boolean;
236+ function ReplaceDockedControl(Control: TControl; NewDockSite: TWinControl;
237+ DropControl: TControl; ControlSide: TAlign): Boolean;
238+ procedure BeginDrag(Immediate: Boolean; Threshold: Integer);
239+ procedure Dock(NewDockSite: TWinControl; ARect: TRect);
240+ procedure DragDrop(Source: TObject; X, Y: Integer);
241+ procedure EndDrag(Drop: Boolean);
242+ property DockOrientation: TDockOrientation;
243+ property Floating: Boolean;
244+ property FloatingDockSiteClass: TWinControlClass;
245+ property HostDockSite: TWinControl;
246+ property LRDockWidth: Integer;
247+ property TBDockHeight: Integer;
248+ property UndockHeight: Integer;
249+ property UndockWidth: Integer;
250+```
251+
252+以下类不存在或不可用:
253+```
254+TDragImageList = class(TCustomImageList)
255+ TDockZone = class
256+ TDockTree = class(TInterfacedObject, IDockManager)
257+ TDragObject = class(TObject)
258+ TBaseDragControlObject = class(TDragObject)
259+ TDragControlObject = class(TBaseDragControlObject)
260+ TDragDockObject = class(TBaseDragControlObject)
261+```
262+
263+以下函数也不可用或不兼容:
264+```
265+function FindDragTarget(const Pos: TPoint;
266+ AllowDisabled: Boolean) : TControl;
267+ procedure CancelDrag;
268+ function IsDragObject(sender: TObject): Boolean;
269+```
270+
271+关于停靠管理的初步知识,请参阅[[Anchor Docking|锚点停靠]]
272+
273+### TEdit/TCustomEdit
274+
275+LCL 的编辑控件虽然在功能上与 VCL 基本相同,但在代码转换时还是有一些问题需要注意:
276+
277+ 1. 由于界面接口的限制,TEdit.PasswordChar 并非都能正常工作(将来可能会有所改善),在需要隐藏文本时应采用将 TCustomEdit.EchoMode 设为 emPassword 的方式。
278+
279+ 1. Drag/Dock 事件处理程序尚未实现。详见前文 控件拖动与停靠
280+
281+ 1. 为保持接口的一致性,Font 属性通常会被忽略。详细原因请参阅 TControl.Font/TControl.ParentFont
282+
283+### TDBImage
284+
285+Delphi 和 Lazarus 都提供了 [[TDBImage]] 控件,用于显示存于数据库字段中的图片。在当前稳定版本(1.2.4)中,Lazarus 会将数据库字段中的图片类别信息存于实际的图片数据之前。请参见 _TDBImage.UpdateData_ 过程的代码。
286+
287+这意味着 Delphi 和旧版 Lazarus 不兼容。
288+
289+目前的 Lazarus 稳定版本(1.2.0+)已允许保持与 Delphi 的兼容性。有关如何启用的详细信息,请参阅[[Lazarus 1.2.0 release notes|Lazarus_1.2.0_release_notes#TDBImage]]。
290+
291+目前的 Lazarus 主版本在将 Delphi 格式的数据库字段读入 TDBImage 时,会回退至与 Delphi 兼容的处理方式。
292+
293+### (可选的)TSplitter -> TPairSplitter
294+
295+**请大家在此完善内容**
296+
297+目前 LCL 中有一个 [[TSplitter]] 控件,因此不需要转换代码。
298+
299+不过下面会作解释:
300+
301+以下内容大致源于邮件列表 [ Vincent Snijders](</User:Vincent> "User:Vincent") 的提问和 [Andrew Johnson](<http://lazarus-ccr.sourceforge.net/index.php?wiki=AndrewJohnson>) 的答复:
302+
303+在 VCL 中,“分隔”控件由 TSplitter 实现,即可供在两个组件之间拖动的控制柄,用于调节两个组件占用空间的比例。这种控件很常见,比如在 Delphi IDE 中,已停靠的 Code Explorer 和 Source Viewer 之间就有一个。
304+
305+LCL提供了自己的控件 TPairSplitter,用途相同但不兼容;因此在移植代码时,需对残缺的 VCL 或 Delphi DFM 代码进行“修正”,当然两者有很多相同之处。
306+
307+**那么到底有哪些不同点呢?**
308+
309+最大的区别是 VCL TSplitter 没有子控件,它只是置于两个对齐的控件之间,以便在运行时调整两边的尺寸,而不考虑自己的尺寸。它的两侧必须有两个对齐的控件。举个简单的例子,假设有一个窗体包含一个左对齐的 Panel、一个左对齐的 Splitter 和一个填充对齐(Client)的 Panel。在运行时,拖动 Splitter 控件的控制柄可以重新调整每个 Panel 的大小。
310+
311+而 LCL 中的 TPairSplitter 是一种特殊的控件,它带有两个 Panel,只有当两个 Panel 上放有需分隔的控件时才有效果;但无论 Panel 上是否有其他控件,仍会对 Panel 进行分隔。因此针对上述例子,将会是窗体包含一个填充对齐的 TPairSplitter,其左右两侧各有一个填充对齐的 Panel。
312+
313+另一个重要区别是,VCL 的 TSplitter 由于本身就是 TControl,在调整大小时相对其他控件的位置会保持不变。于是当一个填充对齐的 Panel 增大而其他 Panel 不变时,分隔条的位置将与被分隔的控件对齐。
314+
315+在 LCL 中,由于两侧 Panel 是分开的,因此 TPairSplitter 带有一个相对左、上侧的 Position 属性。在调整大小时,实际位置不会随内部控件而改动,如果调整大小时保持相对比例很重要,就必须设置回调函数。
316+
317+举个例子,如果纵向分隔条的右侧需要 alClient 对齐模式,则需添加一个窗体大小变动的回调函数,执行类似如下操作:
318+```
319+PairSplitter.Position := PairSplitter.Width - PairSplitter.Position;
320+```
321+
322+**如何将使用 TSplitter 的代码转换为 TPairSplitter?**
323+
324+如果分隔条和控件是在函数(如窗体 OnCreate 时)中创建的,那么转换应该不会很困难,主要工作是重新组织代码,按照新的控件结构创建控件,并将要分隔的子控件的父控件设为 PairSplitter 的左/顶和右/底。以下是个例子:
325+
326+
327+
328+**VCL** | **LCL**
329+---|---
330+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;
331+
332+这与大多数控件层次结构相当一致。如果熟悉 DFM,以上已充分说明了 DFM->LFM 所需的改动,就是对 Parent/Owner 等属性进行同类修改。
333+
334+于是上面的例子应该类似如下:
335+
336+**Delphi DFM** **(extraneous values removed)** | **Lazarus LFM** **(most width, height, etc. removed)**
337+---|---
338+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
339+
340+### TCustomTreeView/TTreeView
341+
342+VCL 和 LCL 都提供了 TCustomTreeView/TTreeView 组件,用于显示多节点的树形结构,可勾选及显示图片。尽管实际功能差不多,但并非所有属性都相互兼容。主要区别如下:
343+
344+**内容并不完整,有待更新 TCustomTreeView 的 Mark 函数和受保护的方法**
345+
346+ 1. LCL 提供 TCustomTreeView.Options 属性,可以设置控件属性,修改外观和功能。包括:
347+ * tvoAllowMultiselect - 启用多选模式,等效于 D6 VCL 中启用 TCustomTreeView.MultiSelect
348+ * tvoAutoExpand - 节点自动展开,等效于启用 TCustomTreeView.AutoExpand
349+ * tvoAutoInsertMark - 鼠标拖动时更新预览
350+ * tvoAutoItemHeight - 自动调整高度
351+ * tvoHideSelection - 不标记选中项
352+ * tvoHotTrack - 启用 Hot Track,等效于启用 TCustomTreeview.HotTrack
353+ * tvoKeepCollapsedNodes - 节点折叠后,留存子节点
354+ * tvoReadOnly - Treeview 只读模式,等效于启用 TCustomTreeview.ReadOnly
355+ * tvoRightClickSelect - 允许选中节点能用鼠标右键点击,等效于启用 TCustomTreeView.RightClickSelect
356+ * tvoRowSelect - 允许选中行,等效于启用 TCustomTreeView.RowSelect
357+ * tvoShowButtons - 显示按钮,等效于启用 TCustomTreeView.ShowButtons
358+ * tvoShowLines - 显示树的竖线,等效于启用 TCustomTreeView.ShowLines
359+ * tvoShowRoot - 显式根节点,等效于启用 TCustomTreeView.ShowRoot
360+ * tvoShowSeparators - 显示树的横线分隔符
361+ * tvoToolTips - 为过长节点显示提示信息
362+
363+ 1. LCL 提供更多属性:
364+ * TCustomTreeView.OnSelectionChange 事件
365+ * TCustomTreeView.DefaultItems,默认节点数(文档中未找到)
366+ * TCustomTreeView.ExpandSignType,用于确定展开/折叠图标
367+
368+ 1. 尽管 LCL 控件中给出了大部分拖动/停靠事件,但并不起作用。详情请参阅前文控件拖动/停靠部分。
369+
370+### 消息 / 事件
371+
372+LCL 的消息和事件(如 OnShow、OnActivate、OnEnter 等)处理顺序和频率与 VCL 不同,且依赖于各平台的可视化[[Widgetset|组件]]。
373+
374+LCL 提供了一组类似 WinAPI 的消息,以简化 Delphi 组件的移植,但几乎所有 LCL 消息的工作方式都与对应的 VCL/WinAPI 略有不同。Delphi 代码之所以要用到 WinAPI 消息,大多数是因为 VCL 缺少某些功能或为了提高性能。在 LCL 中不太会这么干,因此必须进行人工检查。比如 LCL 消息称为 LM_SIZE 而不是 WM_SIZE(lmessages 单元中),就是这个原因。
375+
376+**处理自定义消息时的注意事项!**
377+
378+LCL 自 0.9.26 版本(2008年12月)开始,自定义 WinApi 消息(如 WM_HOTKEY、WM_SYSCOMMAND)的处理方式与 Delphi 不一样了。现在无法通过 **message** 指令或重写窗体 WndProc 方法进行处理了。在窗体中处理自定义消息的唯一方案,就是自行挂钩 Windows 的 _windowproc_ 。详情请阅读:[[Win32/64 Interface|在窗体中处理非用户消息]]。
379+
380+## 参阅
381+
382+ * [[Delphi language features missing from the Free Pascal Compiler|Free Pascal 编译器不支持的 Delphi 语言功能]]
383+ * [[Code Conversion Guide|Delphi & Kylix 代码转换指南]]
384+ * [[Compile With Delphi|用 Delphi 编译]]
385+
386+---
387+*From the Free Pascal wiki, page "Lazarus For Delphi Users/zh CN", as the Internet Archive kept it on 2025-11-27, last changed on 2024-10-22. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history