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
| Old | New | Line | |
|---|---|---|---|
| 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 | + |  **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).* |
