Difference between revisions of "Extending the IDE/zh CN"

From Lazarus wiki
Jump to navigationJump to search
m
m (Fixed syntax highlighting)
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{Extending the IDE}}
 
{{Extending the IDE}}
  
= 扩展IDE =
+
= 扩展 IDE =
  
 
== 概述 ==
 
== 概述 ==
  
IDE支持以下几种插件:
+
Lazarus IDE 支持以下几种类型的插件:
  
; 组件(Components) : 就是那些在控件面板上的东西。例如TButton可以用来创建按钮。
+
; 组件(Components) : 这些是“组件面板”上的项目。例如 TButton(可以在“标准”页面上找到)可用于创建按钮。
  
; 组件编辑器(Component Editors) : 当你在界面设计器(designer)上双击一个控件的时候,就会用到组件编辑器。扩展当右击一个控件时弹出的右键菜单里的条目的时候,也会用到组件编辑器。
+
; 组件编辑器(Component Editors) : 当你在“界面设计器(Designer)”中双击一个组件时(比如“TMainMenu”组件),将调用组件编辑器(比如“菜单编辑器”)。组件编辑器还可以在“界面设计器”的右键菜单中添加一个额外的菜单项(右键单击“界面设计器”中的组件时可以访问)。
  
; 属性编辑器(Property Editors) : 被对象监视器(object inspector)中的那些字段(row)使用。
+
; 属性编辑器(Property Editors) : “对象查看器”的列表中的每一行都使用属性编辑器来设置或更改该属性的值。
  
; 砖家?(Experts) : 这是完全不同于其他的一种,他们注册新的菜单项和快捷方式,或者扩展其它的IDE特性。
+
; 专家(Experts) : 专家包含所有其他插件类型。他们可能会注册新的菜单项,注册快捷方式或扩展其他 IDE 功能。
  
  
有两种方式把你编写的插件添加到Lazarus里:
+
有两种方式可以将自己的插件添加到 Lazarus IDE:
  
# 写一个包(package),安装它,并在你编写的单元中的Register函数中写代码注册之。
+
# 编写一个包,安装它并在包单元的“Register”过程中注册你的插件。
# 扩展lazarus的源代码,然后把svn diff发送到lazarus的邮件列表。
+
# 扩展 Lazarus 代码,并将您的 svn diff 发送到 Lazarus 邮件列表。
  
没有翻译的句子包含不会翻的和不感兴趣懒得翻的,以及还没翻完的。术语之类会在跟随的括号里给出英文。
+
== 包和 Makefile 依赖项 ==
  
=菜单项=
+
如果添加新的包依赖项,则必须执行以下步骤。
  
你可以添加新的菜单项,隐藏或移动已经存在的菜单项,也可以添加子菜单和sections。For your own forms you can register the TMainMenu/TPopupMenu so that other packages can extend your form further. 包IDEIntf中的单元MenuIntf包含了all registration for menus and many standard menu items of the IDE itself.
+
* 创建 / 通过包编辑器更新包的 MakeFile/ 更多 .../ 创建 Makefile。
 +
* 修复所有依赖项。请参阅 tools/updatemakefiles.lpi
  
== 添加一个菜单项 ==
+
= 菜单项 =
  
诸如'View Object Inspector'之类的单独一个菜单项被称作一个'''TIDEMenuCommand'''。你可以使用'''RegisterIDEMenuCommand'''来创建一个这种菜单项,这个函数有两种形式(参数挺多的):
+
您可以添加新菜单项,隐藏或移动现有菜单项并添加子菜单、菜单区(菜单区是整个菜单中的一块区域,它可以是一个包含子菜单的菜单项,也可以是由分隔符分隔出来的一块区域。如果菜单区中没有子项,则该菜单区将不可见。比如 itmFileNew 就是一个用分隔符分隔的菜单区,它里面包含了“新建单元”、“新建窗体”、“新建 ...” 等菜单项,itmFileRecentOpen 也是一个菜单区,它只是一个菜单项,包含“最近打开的文件”子菜单)。对于您自己的窗体,您可以注册 TMainMenu/TPopupMenu,以便其它包可以进一步扩展您的窗体。'''IDEIntf''' 包的 MenuIntf 单元包含菜单的所有注册函数和 IDE 本身的许多标准菜单项。
  
<syntaxhighlight>
+
== 添加菜单项 ==
function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
+
 
                                const Name, Caption: string;
+
像“查看 -> 对象查看器”这样的单个菜单项称为 '''TIDEMenuCommand'''。您可以使用 '''RegisterIDEMenuCommand''' 创建一个,它有两个带有大量参数的格式:
                                const OnClickMethod: TNotifyEvent = nil;
+
 
                                const OnClickProc: TNotifyProcedure = nil;
+
<syntaxhighlight lang=pascal>
                                const Command: TIDECommand = nil;
+
function RegisterIDEMenuCommand(
                                const ResourceName: String = ''
+
    Parent: TIDEMenuSection;
                                ): TIDEMenuCommand; overload;
+
    const Name, Caption: string;
function RegisterIDEMenuCommand(const Path, Name, Caption: string;
+
    const OnClickMethod: TNotifyEvent = nil;
                                const OnClickMethod: TNotifyEvent = nil;
+
    const OnClickProc: TNotifyProcedure = nil;
                                const OnClickProc: TNotifyProcedure = nil;
+
    const Command: TIDECommand = nil;
                                const Command: TIDECommand = nil;
+
    const ResourceName: String = ''
                                const ResourceName: String = ''
+
    ): TIDEMenuCommand; overload;
                                ): TIDEMenuCommand; overload;
+
 
 +
function RegisterIDEMenuCommand(
 +
    const Path, Name, Caption: string;
 +
    const OnClickMethod: TNotifyEvent = nil;
 +
    const OnClickProc: TNotifyProcedure = nil;
 +
    const Command: TIDECommand = nil;
 +
    const ResourceName: String = ''
 +
    ): TIDEMenuCommand; overload;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
这两种形式的区别只有你指定父菜单(parent menu section,俺就是不懂section是个么意思)的方式。你可以通过路径直接或间接给定menu section。许多标准的sections可以在单元''MenuIntf''中被找到。例如section '''mnuTools''',就是主IDE窗口上的工具(Tools)菜单。它包含一个名为'''itmSecondaryTools'''的section, 这个section是第三方工具被推荐使用的section。
+
这两者之间的区别仅在于指定父菜单区的方式。您可以通过 Path 参数直接或间接地给出菜单区。''MenuIntf'' 单元中可以找到许多标准菜单区。例如 '''mnuTools''' 菜单区,它是主 IDE 栏的 ''Tools'' 菜单。它包含一个子菜单区 '''itmSecondaryTools''',用于第三方工具的推荐菜单区。
  
下面的代码注册了一个新的菜单命令(menu command),名为''MyTool'',标题为''Start my tool'',但被点击会执行过程''StartMyTool'':
+
下面的代码注册了一个新的菜单命令,菜单名(Name)为“MyTool”,菜单标题(Caption)为“我的工具(&T)”,点击后执行 StartMyTool 过程:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
procedure StartMyTool;
 
procedure StartMyTool;
 
begin
 
begin
   ...executed when menu item is clicked...
+
   ... 点击菜单项后要执行的代码 ...
 
end;
 
end;
  
 
procedure Register;
 
procedure Register;
 
begin
 
begin
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Start my tool',nil,@StartMyTool);
+
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', ' 我的工具(&T)', nil, @StartMyTool);
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
如果你想在点击后调用一个对象的方法而非这种过程(procedure),使用OnClickMethod参数。这个菜单项没有快捷键。如果你需要添加快捷键支持,你必须创建一个TIDECommand并传给Command参数。例如:
+
如果要调用方法而不是过程,请使用 '''OnClickMethod''' 参数。
 +
 
 +
此菜单项没有快捷键。如果需要快捷键,则必须创建一个 '''TIDECommand''' 对象并将其传递给 '''Command''' 参数。例如:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  ... Classes, LCLType, Dialogs, IDECommands, MenuIntf;
  
<syntaxhighlight>
 
uses ... IDECommands, MenuIntf;
 
 
...
 
...
 +
 +
procedure StartMyTool(Sender :TObject);
 +
begin
 +
  ShowMessage('Hello World!');
 +
end;
 +
 +
procedure Register;
 
var
 
var
 
   Key: TIDEShortCut;
 
   Key: TIDEShortCut;
Line 73: Line 92:
 
   CmdMyTool: TIDECommand;
 
   CmdMyTool: TIDECommand;
 
begin
 
begin
   // register IDE shortcut and menu item
+
   // 注册 IDE 快捷键(Ctrl+Alt+Shift+T)和菜单项
   Key := IDEShortCut(VK_UNKNOWN,[],VK_UNKNOWN,[]);
+
   Key := IDEShortCut(VK_T,[ssCtrl,ssAlt,ssShift],VK_UNKNOWN,[]);
   Cat:=IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
+
   Cat := IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
   CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
+
   CmdMyTool := RegisterIDECommand(Cat, 'StartMyTool', ' 启动我的工具 ', Key, nil, @StartMyTool);
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
+
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', ' 我的工具 (&T)', nil, nil, CmdMyTool);
 +
end;
 +
</syntaxhighlight>
 +
 
 +
== 快捷键 ==
 +
 
 +
所有快捷键都在 IDECommandList 中注册。下面是一个示例,将查找所有使用 Ctrl-D 作为快捷键的命令:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  Classes, SysUtils, Dialogs, LCLType, LCLProc, IDECommands, MenuIntf;
 +
 
 +
// 列出所有使用 Ctrl-D 的 IDE 命令
 +
procedure ListCtrlD;
 +
var
 +
  i        :integer;
 +
  Cmd      :TIDECommand;
 +
  Commands :TFPList;
 +
begin
 +
  Commands := IDECommandList.FindCommandsByShortCut(IDEShortCut(VK_D, [ssCtrl], VK_UNKNOWN, []));
 +
  try
 +
    for i := 0 to Commands.Count - 1 do
 +
    begin
 +
      Cmd := TIDECommand(Commands[i]);
 +
      ShowMessage(format('Cmd: %s, A = %s - %d, %s - %d' + #13#10 + 'B = %s - %d, %s - %d',
 +
      [Cmd.Name,
 +
      dbgs(Cmd.ShortcutA.Shift1),
 +
      Cmd.ShortcutA.Key1,
 +
      dbgs(Cmd.ShortcutA.Shift2),
 +
      Cmd.ShortcutA.Key2,
 +
      dbgs(Cmd.ShortcutB.Shift1),
 +
      Cmd.ShortcutB.Key1,
 +
      dbgs(Cmd.ShortcutB.Shift2),
 +
      Cmd.ShortcutB.Key2]));
 +
    end;
 +
  finally
 +
    Commands.Free;
 +
  end;
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=配置文件=
+
= 配置文件 =
  
== 加载和保存配置 ==
+
加载和保存配置
  
所有IDE的配置文件都以xml的形式放在同一个目录下。包们可以把它们自己的配置文件也放在那里。那个目录可以通过如下形式获取:
+
IDE 的所有配置文件都以 xml 格式存储在一个目录中(主配置目录)。包也可以在那里添加自己的配置文件。可以使用下面的方式读取“主配置目录”:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses LazIDEIntf;
 
uses LazIDEIntf;
 
...
 
...
   Directory:=LazarusIDE.GetPrimaryConfigPath;
+
   Directory := LazarusIDE.GetPrimaryConfigPath;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
包们可以通过如下形式创建它们自己的xml格式配置文件:
+
包可以创建自己的 xml 文件:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., LCLProc, BaseIDEIntf, LazConfigStorage;
 
   ..., LCLProc, BaseIDEIntf, LazConfigStorage;
Line 116: Line 172:
 
     Config:=GetIDEConfigStorage('mysettings.xml',false);
 
     Config:=GetIDEConfigStorage('mysettings.xml',false);
 
     try
 
     try
       // 保存版本号,以便未来的扩展可以兼容处理它们
+
       // 存储版本号,以便将来的扩展可以处理旧的配置文件
 
       Config.SetDeleteValue('Path/To/The/Version',Version,0);
 
       Config.SetDeleteValue('Path/To/The/Version',Version,0);
       // 保存字符串字段"SomeValue"
+
       // 存储字符串变量 “someValue”
       // 如果SomeValue的值(第二个参数)就是默认值(第三个参数),这个字段就不会被保存,
+
       // 如果 someValue 的值和默认值相同,则不会存储该项
       // 因此只有与默认值不同的字段才会被保存
+
       // 因此只有与默认值不同的项才会被存储
       // 这样一来xml可以尽量短小,默认值在未来换起来也方便
+
       // 这样能使 XML 保持简短,并且便于在将来改变默认值。
 
       Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
 
       Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
 
       Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
 
       Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
 
       Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
 
       Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
       // SetDeleteValue还有其他的重载形式,按下Ctrl+Space自个儿琢磨吧(我勒个去这货不是输入法这货不是输入法……)
+
       // 还有很多 SetDeleteValue 的重载,通过 Ctrl+Space(代码提示的快捷键)查看它们。
 
     finally
 
     finally
 
       Config.Free;
 
       Config.Free;
Line 142: Line 198:
 
       // 读取配置的版本
 
       // 读取配置的版本
 
       FileVersion:=Config.GetValue('Path/To/The/Version',0);
 
       FileVersion:=Config.GetValue('Path/To/The/Version',0);
       // 读取字符串字段"SomeValue",如果不存在,使用默认值
+
       // 读取字符串变量“SomeValue”。如果该项不存在,则使用默认值。
 
       SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
 
       SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
 
       SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
 
       SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
Line 157: Line 213:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=组件=
+
注意:
 +
* 每个 IDE 插件都应该使用自己的配置文件。特别是您不能使用标准 IDE 选项文件,如 editoroptions.xml 或 environmentoptions.xml。
 +
* GetIDEConfigStorage 在加载时,会检查文件是否存在,如果不存在,它将从辅助配置目录复制模板。
 +
* 从 0.9.31 开始,您可以使用此函数在主配置目录之外加载文件。
 +
 
 +
= 组件 =
 +
 
 +
== 扩展现有组件 ==
 +
 
 +
来源:[http://lazarus.freepascal.org/index.php/topic,17733.msg98425.html#msg98425] 定制现有的 LCL 组件很简单,例如,如果要添加属性。
 +
 
 +
使用字段及其相应的 setter 和 getter 方法来添加所需的属性。
 +
 
 +
然后“手动”实例化它们,给出一个 OWner 组件(以便处理稍后的销毁操作),并设置 Parent 和可能的 Top 和 Left 以及组件在初始化时需要的任何其他默认值。
 +
 
 +
如果您对自定义组件的调试感到满意,请添加一个 RegisterComponent() 调用以将其加入到 IDE 的组件面板中,并保存您必须“手动”编写的设置代码(查看任一个组件的源代码以了解如何执行此操作)。
 +
 
 +
这是一个简单的例子:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit Unit1;
 +
 
 +
{$mode objfpc}{$H+}
 +
 
 +
interface
 +
 
 +
uses
 +
  Classes, SysUtils, Forms, StdCtrls;
 +
 
 +
type
 +
  TEdX = class(TEdit)
 +
  private
 +
    FNewIntProp: integer;
 +
    FNewStrProp: string;
 +
    procedure SetNewIntProp(AValue: integer);
 +
    procedure SetNewStrProp(AValue: string);
 +
  public
 +
    constructor Create(theComponent: TComponent); override;
 +
    property NewIntProp: integer read FNewIntProp write SetNewIntProp;
 +
    property NewStrProp: string read FNewStrProp write SetNewStrProp;
 +
  end;
 +
 
 +
  TForm1 = class(TForm)
 +
    procedure FormCreate(Sender: TObject);
 +
  private
 +
    edX: TEdX;
 +
  public
 +
  end;
 +
 
 +
var
 +
  Form1: TForm1;
 +
 
 +
implementation
 +
 
 +
{ TForm1 }
 +
 
 +
procedure TForm1.FormCreate(Sender: TObject);
 +
begin
 +
  edX:= TEdX.Create(Self);
 +
  edX.Parent:= Self;
 +
  edX.Caption:= IntToStr(edX.NewIntProp);
 +
  Caption:= edX.NewStrProp;
 +
end;
 +
 
 +
{$R *.lfm}
 +
 
 +
{ TEdX }
 +
 
 +
procedure TEdX.SetNewIntProp(AValue: integer);
 +
begin
 +
  if FNewIntProp=AValue then Exit;
 +
  FNewIntProp:=AValue;
 +
  // 这里填写自定义代码
 +
end;
 +
 
 +
procedure TEdX.SetNewStrProp(AValue: string);
 +
begin
 +
  if FNewStrProp=AValue then Exit;
 +
  FNewStrProp:=AValue;
 +
  // 这里填写自定义代码
 +
end;
 +
 
 +
constructor TEdX.Create(theComponent: TComponent);
 +
begin
 +
  inherited Create(theComponent);
 +
  FNewStrProp:= 'new string property value';
 +
  FNewIntProp:= 99;
 +
end;
 +
 
 +
end.
 +
</syntaxhighlight>
  
 
== 编写组件 ==
 
== 编写组件 ==
你可以通过包编辑器(package editor)创建新组件。例如:要创建或打开一个包,点击add,再点New Component,填写条目:Ancestor Type = TButton,New class name = TMyButton,palette page = Misc,Unit file name = mybutton.pas (这个文件不会被创建的),单元名称设置为MyButton然后点击ok。
 
(想起小时候看的一本win95还是win98的书,那里面双击叫双咔嗒,单击叫单咔嗒。题外话,可乐加怀旧。)
 
  
=== 给这个新组件添加个图标(显示在控件面板上) ===
+
主要文章:[[How To Write Lazarus Component]]
 +
 
 +
您可以通过包编辑器创建新组件。
  
For example give TMyButton an icon. Create an image file of the format .bmp, .xpm or .png with the same name as the component class. For example ''tmybutton.png'' and save it in the package source directory. The image can be created by any graphic program (e.g. gimp) and should be no bigger than 24x24 pixel.
+
创建或打开一个包,单击“添加 -> 新组件”,填写项目:
Then convert the image to a .lrs file with the lazres tool, which can be found in the lazarus/tools directory:
+
Ancestor Type = TButton,
  ~/lazarus/tools/lazres mybutton.lrs tmybutton.png
+
New class name = TMyButton,
 +
palette page = Misc,
 +
Unit name = mybutton.pas(将创建此文件) ,
 +
单位名称 MyButton,然后单击确定。
  
This creates an pascal include file, which is used in the initialization section of mybutton.pas:
+
=== 为组件面板提供一个新组件图标 ===
  
  initialization
+
创建格式为 .bmp、.xpm 或 .png 的图像文件,其名称与组件类相同。例如 tmybutton.png 并将其保存在包源目录中。图像可以由任何图形程序(例如 gimp)创建,并且不应大于 24x24 像素。然后使用 lazres 工具将图像转换为资源文件,该工具可以在 lazarus/tools 目录中找到:
    {$I mybutton.lrs}
 
  
Install the package.
+
<syntaxhighlight lang="bash">
 +
~/lazarus/tools/lazres mybutton.lrs tmybutton.png
 +
# note: Laz trunk/1.4+? uses .res resource files instead of .lrs files:
 +
~/lazarus/tools/lazres mybutton.res tmybutton.png
 +
</syntaxhighlight>
  
===在控件面板上隐藏一个组件===
+
这将创建一个 pascal include 文件,该文件在 mybutton.pas 的初始化部分中使用:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
initialization
 +
  {$I mybutton.lrs}
 +
  // 或在较新的 Lazarus 版本中 - to docheck 语法
 +
  {$R mybutton.res}
 +
</syntaxhighlight>
 +
 
 +
安装包。
 +
 
 +
=== 隐藏组件面板中的组件 ===
  
 
Package IDEIntf, unit componentreg:
 
Package IDEIntf, unit componentreg:
IDEComponentPalette.FindComponent('TButton').Visible:=false;
 
  
== 编写组件编辑器(component editors) ==
+
<syntaxhighlight lang=pascal>
 +
  IDEComponentPalette.FindComponent('TButton').Visible:=false;
 +
</syntaxhighlight>
 +
 
 +
== 编写组件编辑器 ==
  
组件编辑器应该用来处理双击界面编辑器中的一个控件,或者处理添加右键菜单项(在右击一个组件的时候)。编写一个组件编辑器是很简单的。
+
组件编辑器处理诸如双击设计器中的组件或右键单击组件时添加菜单项之类的操作。编写组件编辑器很简单。有关大量示例,请参阅 IDEIntf 包的 componenteditors.pas 单元。例如,双击时显示编辑器,请参阅 TCheckListBoxComponentEditor。
看看包IDEIntf中的单元componenteditors.pas,里面很多例子。例如双击组件时唤出代码编辑器,CheckListBox的实现是TCheckListBoxComponentEditor。
 
  
 
== 编写属性编辑器 ==
 
== 编写属性编辑器 ==
  
在对象观察器(Object Inspector)中的每种类型(integer, string, TFont, ...)的属性都需要一个属性编辑器。如果你的组件包含一个名为Password的字符串类型的属性,你得相应地定义一个属性编辑器。
+
“属性查看器”中的每种属性(整数、字符串、TFont、...)都需要属性编辑器。如果组件具有字符串类型的属性 Password,则可以为特定组件类定义属性编辑器,并使用字符串类型的名为 Password 的属性。在 IDEIntf 的 propedits.pp 单元中包含 IDE 本身使用的许多标准属性编辑器。例如 TStringPropertyEditor 是所有字符串的默认属性编辑器,而 TComponentNamePropertyEditor 更有针对性,它只处理 TComponent.Name。请参阅示例中的单元文件。
包IDEIntf中的单元propedits.pp包含很多标准的正被IDE使用的属性编辑器。例如,TStringPropertyEditor是所有字符串值的默认编辑器,但TComponentNamePropertyEditor就会更有针对性,它只处理TComponent.Name。
+
 
 +
=== 在 Object Inspector 中隐藏属性 ===
 +
 
 +
有一个特殊的属性编辑器用于隐藏:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
  RegisterPropertyEditor(TypeInfo(TAnchorSide), TControl, 'AnchorSideLeft', THiddenPropertyEditor);
 +
</syntaxhighlight>
 +
 
 +
=== 找出属性使用的属性编辑器 ===
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses Classes, TypInfo;
 +
var
 +
  Instance: TPersistent; // 你的实例
 +
  PropInfo: PPropInfo;
 +
  EdClass: TPropertyEditorClass;
 +
begin
 +
  PropInfo:=GetPropInfo(Instance.ClassType,'Name'); // 将 Name 替换为您要搜索的属性名称
 +
  GetEditorClass(PropInfo, Instance);
 +
end;
 +
</syntaxhighlight>
 +
 
 +
== 窗体 ==
 +
 
 +
IDE 允许轻松创建窗体和框架的后代。这些窗体可以包含新的 published 组件和新的 published 方法。但是,当您添加新的 published 属性时,无法通过“对象查看器”访问它们。它们仅在运行时工作。IDE 中的方法是伪造的,无法执行。IDE 可以创建组件,因为它们的类已在组件面板中注册。你的新属性仅存在于源代码中,IDE 还不能伪造它们。这就是你必须注册新类的原因。这意味着:
 +
 
 +
* 创建设计时包(或扩展现有包)
 +
* 您的窗体必须在此包的单元中
 +
 
 +
<syntaxhighlight lang=pascal>
 +
...
 +
interface
 +
 
 +
uses FormEditingIntf, Forms;
 +
 
 +
type
 +
  TYourForm = class(TForm)
 +
  private
 +
    FYourProperty: integer;
 +
  published
 +
    property YourProperty: integer read FYourProperty write FYourProperty;
 +
  end;
 +
 
 +
procedure Register;
 +
 
 +
implementation
 +
 
 +
procedure Register;
 +
begin
 +
FormEditingHook.RegisterDesignerBaseClass(TYourForm);
 +
end;
 +
...
 +
</syntaxhighlight>
 +
 
 +
* 检查包编辑器中本单元的“Register”。
 +
* 在 IDE 中安装包
  
=设计器(Designer)=
+
= 界面设计器(Designer) =
  
== 编写一个设计器中间层(mediator,调解着,中介) ==
+
=== 编写一个设计器中间层 ===
  
The standard designer allows to visually edit LCL controls, while all others are shown as icons. To visually edit non LCL control, you can create a '''designer mediator'''. This can be used to design webpages, UML diagrams or other widgetsets like [http://opensoft.homeip.net/fpgui/ fpGUI].
+
标准设计器允许可视化编辑 LCL 控件,而所有其它控件都显示为图标(如 TOpenDialog 或 TDataSource)。要可视地编辑非 LCL 控件,您需要创建一个 ''' 设计器中间层 '''。这可用于设计网页,UML 图或其它小部件集,如 [[fpGUI]]。在 examples/designnonlcl/ 中有一个完整的例子。
There is a complete example in examples/designnonlcl/.
 
  
*Install the example package examples/designnonlcl/notlcldesigner.lpk and restart the IDE. This will register the designer mediator for TMyWidget components and add the new components TMyButton, TMyGroupBox to the component palette.
+
* 安装示例包 examples/designnonlcl/notlcldesigner.lpk 并重新启动 IDE。这将为 TMyWidget 组件注册设计器中间层,并将新组件 TMyButton,TMyGroupBox 添加到组件面板中。
*Open the the example project examples/designnonlcl/project/NonLCL1.lpi.
+
* 打开示例项目 examples/designnonlcl/project/NonLCL1.lpi。
*Open the unit1.pas and show the designer form (F12). You should see the components as red rectangles, which can be selected, moved and resized like LCL controls.
+
* 打开 unit1.pas 并切换到设计器界面(F12)。您应该看到组件为红色矩形,可以像 LCL 控件一样选择,移动和调整大小。
 +
 
 +
== 创建一个新的唯一组件名称 ==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses FormEditingIntf;
 +
 
 +
...
 +
 
 +
NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);
 +
 
 +
// 或者如果您需要在创建组件之前创建名称:
 +
 
 +
NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
 +
// ComponentClassName 将用作前缀,没有前导 T.
 +
// OwnerComponent 是新组件的新所有者。
 +
</syntaxhighlight>
  
== 如何在设计观察器和对象观察器中选择一个组件 ==
+
== 在设计器 / 对象检查器中选择一个组件 ==
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>uses propedits;
uses propedits;
 
 
..
 
..
 
GlobalDesignHook.SelectOnlyThis(AComponent);
 
GlobalDesignHook.SelectOnlyThis(AComponent);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
= 事件处理 =
+
== 获取单元,设计器,文件的形式 ==
 +
 
 +
<syntaxhighlight lang=pascal>uses ProjectIntf, LazIDEIntf, ComponentEditors;
 +
...
 +
// 打开文件
 +
if LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent])<>mrOk then exit;
 +
// 获取文件界面
 +
aFile:=LazarusIDE.ActiveProject.FindFile(Filename,[]);
 +
// 获取设计器
 +
aDesigner:=TComponentEditorDesigner(LazarusIDE.GetDesignerWithProjectFile(AFile,true));
 +
if aDesigner=nil then
 +
  exit; // 这个单元没有资源(形式,框架,数据模块等)
 +
// 获取表单
 +
aForm:=aDesigner.Form;
 +
</syntaxhighlight>
 +
 
 +
== 保存单元,设计器窗口 ==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses LazIDEIntf;
 +
...
 +
if LazarusIDE.DoSaveEditorFile(Filename,[])<>mrOk then exit;
 +
</syntaxhighlight>
 +
 
 +
== 将设计器窗口保存为 Pascal ==
 +
 
 +
* 使用 ''FormEditingfIntf'' 单元的 '''FormEditingHook.SaveComponentAsPascal''' 函数。请参阅 ''examples/pascalstream/CopyAsPasPkg/copyaspasdemounit1.pas''。
 +
 
 +
= 事件处理程序 =
 +
 
 +
IDE 中有几个事件,插件可以为其添加自己的处理程序。
 +
 
 +
== 设计器活动 ==
 +
 
 +
在 propedits.pp 中有一个“GlobalDesignHook”对象,它维护了几个用于设计的事件。每个事件都会调用一个处理程序列表。IDE 添加了默认处理程序。您可以使用 AddHandlerXXX 和 RemoveHandlerXXX 方法添加自己的处理程序。它们将在默认处理程序之前调用。
 +
 
 +
例子:
 +
 
 +
* 添加处理程序(通常在对象的构造函数中完成):
 +
  GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);
 +
* 删除处理程序:
 +
  GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);
 +
* 您可以一次删除所有处理程序。例如,在对象的析构函数中添加此行是个好主意:
 +
  GlobalDesignHook.RemoveAllHandlersForObject(Self);
 +
 
 +
GlobalDesignHook 的处理程序:
 +
 
 +
* 查找根目录
 +
** ChangeLookupRoot - 当“LookupRoot”发生变化时调用。“LookupRoot”是当前所选组件的所有者对象。通常这是一个 TForm。
 +
 
 +
* 方法
 +
** CreateMethod
 +
** GetMethodName
 +
** 的 getMethods
 +
** MethodExists
 +
** RenameMethod
 +
** ShowMethod
 +
** MethodFromAncestor
 +
** ChainCall
  
IDE中有一些事件是可以被你编写的插件处理的。
+
* 组件
 +
** GetComponent
 +
** GetComponentName
 +
** GetComponentNames
 +
** GetRootClassName
 +
** AddClicked - 在用户选择组件类并单击设计器以添加新组件时调用。您可以更改组件类和父组件。
 +
** ComponentRenamed - 在重命名组件时调用
  
== 界面设计器事件 ==
+
* TPersistent 对象
 +
** PersistentAdded - 在将新的 TPersistent 添加到 LookupRoot 时调用
 +
** PersistentDeleting - 在释放 TPersistent 之前调用。
 +
** DeletePersistent - 由 IDE 调用以删除 TPersistent。
 +
** GetSelectedPersistents - 获取当前 TPersistent 选择时调用。
 +
** SetSelectedPersistents - 在设置 TPersistent 的当前选择时调用。
 +
** GetObject
 +
** GetObjectName
 +
** GetObjectNames
  
在propedits.pp中有个"GlobalDesignHook"对象,它维护了几个和界面设计相关的事件。每个事件(event)会调用一个队列的事件处理(handler)。默认的事件处理是IDE添加的。你可以用AddHandlerXXX和RemoveHandlerXXX方法来添加你自己的事件处理。它们会在默认的事件处理被调用之前而被调用。
+
* 修改
 +
** Modified
 +
** Revert
 +
** RefreshPropertyValues
  
例子一枚:
+
* 选区
 +
** SetSelection
 +
** GetSelection
  
   添加你的handler(通常在构造函数干这事儿):
+
=== 设计器活动的例子 ===
     GlobalDesignHook.AddHandlerComponentAdded(@YourOnComponentAdded);<br>    
+
 
   删除你的handler:
+
==== 示例:当放置新组件时,将新组件移动到子组件中 ====
    GlobalDesignHook.RemoveHandlerComponentAdded(@YourOnComponentAdded);<br>
+
 
   你可以一下干掉所有的handlers。例如,把这行加到你的析构函数里应该是个好点子:
+
<syntaxhighlight lang=pascal>
    GlobalDesignHook.RemoveAllHandlersForObject(Self);
+
...
 +
type
 +
  TYourDesignerExtension = class
 +
   private
 +
     function AddClicked(ADesigner: TIDesigner;
 +
            MouseDownComponent: TComponent; Button: TMouseButton;
 +
            Shift: TShiftState; X, Y: Integer;
 +
            var AComponentClass: TComponentClass;
 +
            var NewParent: TComponent): boolean of object;
 +
   public
 +
    constructor Create;
 +
    destructor Destroy; override;
 +
   end;
 +
 
 +
...
 +
constructor TYourDesignerExtension.Create;
 +
begin
 +
   // 注册你的处理程序
 +
  GlobalDesignHook.AddHandlerAddClicked(@AddClicked);
 +
end;
  
GlobalDesignHook的handler们:
+
destructor TYourDesignerExtension.Destroy;
 +
begin
 +
  // 删除你的处理程序
 +
  GlobalDesignHook.RemoveHandlerAddClicked(@AddClicked);
 +
end;
  
  // lookup root
+
function TYourDesignerExtension.AddClicked(ADesigner: TIDesigner;
  ChangeLookupRoot
+
            MouseDownComponent: TComponent; Button: TMouseButton;
    Called when the "LookupRoot" changed.
+
            Shift: TShiftState; X, Y: Integer;
    The "LookupRoot" is the owner object of the currently selected components.
+
            var AComponentClass: TComponentClass;
    Normally this is a TForm.<br>
+
            var NewParent: TComponent): boolean of object;
   // methods
+
begin
   CreateMethod
+
   // 在此示例中,TYourControl 是一个带有 ChildControl 的自定义控件。
  GetMethodName
+
   // 每当用户删除 TYourControl 上的控件而不是将新控件添加到 TYourControl 时,新控件将被添加为 ChildControl 的子控件。
  GetMethods
+
   // 已创建
  MethodExists
+
   if MouseDownComponent is TYourControl then
  RenameMethod
+
     NewParent:=TYourControl(MouseDownComponent).ChildControl;
  ShowMethod
+
   Result:=true;
    Called
+
end;
  MethodFromAncestor
+
</syntaxhighlight>
  ChainCall<br>
 
   // components
 
   GetComponent
 
  GetComponentName
 
  GetComponentNames
 
  GetRootClassName
 
  ComponentRenamed
 
    Called when a component was renamed
 
  ComponentAdded
 
    Called when a new component was added to the LookupRoot
 
  ComponentDeleting
 
     Called before a component is freed.
 
   DeleteComponent
 
    Called by the IDE to delete a component.
 
  GetSelectedComponents
 
    Get the current selection of components.<br>
 
  // persistent objects
 
  GetObject
 
  GetObjectName
 
  GetObjectNames<br>
 
  // modifing
 
  Modified
 
  Revert
 
  RefreshPropertyValues
 
  // Selection
 
  SetSelection
 
  GetSelection
 
  
== 如何在一个设计窗体被修改后得到通知 ==
+
=== 禁用设计器鼠标处理程序 ===
  
每一个设计好的LCL窗体有一个TIDesigner类型的设计器。IDE创建的设计器都是TComponentEditorDesigner类型的,该类型定义在包IDEIntf中的单元componenteditors。
+
通常,设计人员会捕获组件的所有鼠标事件。如果您的自定义控件应该处理鼠标事件句柄,您可以设置 '''csDesignInteractive''' ''' 控件样式 ''',也可以通过控件中的消息 CM_DESIGNHITTEST 对其进行精确控制:
例如:
 
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 +
type
 +
  TYourControl = class(TCustomControl)
 +
  protected
 +
    procedure CMDesignHitTest(var Message: TLMessage); message CM_DESIGNHITTEST;
 +
  end;
 +
...
 +
procedure TYourControl.CMDesignHitTest(var Message: TLMessage);
 +
var
 +
  p: TSmallPoint;
 +
  aShiftState: TShiftState;
 +
begin
 +
  aShiftState:=KeysToShiftState(PtrUInt(Message.WParam));
 +
  p:=TSmallPoint(longint(Message.LParam));
 +
  debugln(['TForm1.CMDesignHitTest ShiftState=',dbgs(aShiftState),' x=',p.x,' y=',p.y]);
 +
  if SkipDesignerMouseHandler then
 +
    Message.Result:=1; // 现在设计器调用了正常的 MouseUp、MouseMove 和 MouseDown 方法。
 +
end;
 +
</syntaxhighlight>
 +
 
 +
== 在修改设计器窗体时收到通知 ==
 +
 
 +
每个设计的 LCL 表格都有一个 TIDesigner 类型的设计器。IDE 将创建在 IDEIntf 单元组件中定义的 TComponentEditorDesigner 类型的设计器。例如:
 +
 
 +
<syntaxhighlight lang=pascal>
 
procedure TYourAddOn.OnDesignerModified(Sender: TObject);
 
procedure TYourAddOn.OnDesignerModified(Sender: TObject);
 
var
 
var
Line 294: Line 625:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Project events ==
+
== 项目事件 ==
 +
 
 +
这些事件在单元 LazIDEIntf 中定义。
 +
 
 +
* LazarusIDE.AddHandlerOnProjectClose:在项目关闭之前调用
 +
* LazarusIDE.AddHandlerOnProjectOpened:在项目完全打开后调用(例如,加载了所有必需的包,在源代码编辑器中打开了单元)
 +
* LazarusIDE.AddHandlerOnSavingAll:在 IDE 保存所有内容之前调用
 +
* LazarusIDE.AddHandlerOnSavedAll:在 IDE 保存所有内容后调用
 +
* LazarusIDE.AddHandlerOnProjectBuilding:在 IDE 构建项目之前调用
 +
* LazarusIDE.AddHandlerOnProjectDependenciesCompiling:在 IDE 编译项目的包依赖项之前调用
 +
* LazarusIDE.AddHandlerOnProjectDependenciesCompiled:在 IDE 编译包项目依赖项后调用
 +
* LazarusIDE.AddHandlerOnProjectBuildingFinished:在 IDE 构建项目后调用(成功与否)(自 1.5 起)
 +
 
 +
== 其他 IDE 事件 ==
 +
 
 +
uses LazIDEIntf;
  
These events are defined in unit LazIDEIntf.
+
在包的注册过程中调用这些:
  
*'''LazarusIDE.AddHandlerOnProjectClose''': called before a project is closed
+
* LazarusIDE.AddHandlerOnIDERestoreWindows:在 IDE 恢复其窗口时调用(在打开第一个项目之前)
*'''LazarusIDE.AddHandlerOnProjectOpened''': called after the project was completely opened (for example all required packages were loaded, units were opened in the source editor)
+
* LazarusIDE.AddHandlerOnIDEClose:在 IDE 关闭时调用(在 closequery 之后调用,因此不再进行交互)
*'''LazarusIDE.AddHandlerOnSavingAll''': called before IDE saves everything
+
* LazarusIDE.AddHandlerOnQuickSyntaxCheck:执行菜单项或快速语法检查的快捷方式时调用
*'''LazarusIDE.AddHandlerOnSavedAll''': called after IDE saved everything
+
* LazarusIDE.AddHandlerOnLazarusBuilding:// 在 IDE 构建 Lazarus IDE 之前调用(从 1.5 开始)
*'''LazarusIDE.AddHandlerOnProjectBuilding''': called before IDE builds the project
+
* LazarusIDE.AddHandlerOnLazarusBuildingFinished:// 在 IDE 构建 Lazarus IDE 后调用(自 1.5 起)
*'''LazarusIDE.AddHandlerOnProjectDependenciesCompiling''': called before IDE compiles package dependencies of project
+
* LazarusIDE.AddHandlerGetFPCFrontEndParams:// 当 IDE 获取 'fpc' 前端工具的参数时调用(自 1.5 起)
*'''LazarusIDE.AddHandlerOnProjectDependenciesCompiled''': called after IDE compiled package dependencies of project
+
* LazarusIDE.AddHandlerGetFPCFrontEndPath:// 当 IDE 获取 'fpc' 前端工具的路径时调用(从 1.5 开始)
 +
* LazarusIDE.AddHandlerOnUpdateIDEComponentPalette:?
 +
* LazarusIDE.AddHandlerOnUpdateComponentPageControl :?
 +
* LazarusIDE.AddHandlerOnShowDesignerFormOfSource:// 在显示代码编辑器的设计器表单后调用(AEditor 可以是 nil!)(自 1.5 起)
 +
* LazarusIDE.AddHandlerOnShowSourceOfActiveDesignerForm:// 显示设计器表单代码后调用(自 1.5 起)
 +
* LazarusIDE.AddHandlerOnChangeToolStatus:// 在 IDEToolStatus 发生变化时调用(例如 itNone-> itBuilder 等)(自 1.5 起)
  
== Other IDE events ==
+
在您的某个单元的初始化部分中调用它们:
  
*'''LazarusIDE.AddHandlerOnIDERestoreWindows''': called when IDE is restores its windows (before opening the first project)
+
* AddBootHandler
*'''LazarusIDE.AddHandlerOnIDEClose''': called when IDE is shutting down (after closequery, so no more interactivity)
+
* AddBootHandler(libhTransferMacrosCreated, @YourProc):创建 IDEMacros 后调用(自 1.3 起)
*'''LazarusIDE.AddHandlerOnQuickSyntaxCheck''': called when the menu item or the shortcut for Quick syntax check is executed
+
* AddBootHandler(libhEnvironmentOptionsLoaded, @YourProc):加载环境选项后调用(自 1.3 起)
  
=Project=
+
= 项目 =
  
==Current Project==
+
== 当前的项目 ==
  
The current main project can be obtained by LazarusIDE.ActiveProject. (unit LazIDEIntf)
+
目前的主要项目可以通过 LazarusIDE.ActiveProject 获得。(单元 LazIDEIntf)
  
==All units of current project==
+
== 当前项目的所有单元 ==
  
To iterate through all pascal units of the current main project of the IDE you can use for example:
+
请注意,术语“项目单元”是不明确的。
  
<syntaxhighlight>
+
* 项目检查器显示 .lpi 文件的列表。该项目可能不会在所有平台上使用所有这些单元,也许开发人员忘记添加所有使用过的单元。
uses LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
+
* 使用过的包中的单元不是项目单元。
 +
 
 +
=== 项目检查员的所有单元 ===
 +
 
 +
要遍历项目检查器中列出的所有 pascal 单元,您可以使用例如:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
  
 
procedure ListProjectUnits;
 
procedure ListProjectUnits;
Line 344: Line 703:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==The .lpr, .lpi and .lps file of a project==
+
=== 所有用过的项目单元 ===
 +
 
 +
要查找项目 / 包的所有当前使用单元,您可以使用以下内容。请注意,IDE 需要解析所有单元,因此可能需要一些时间。
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  LazLoggerBase, LazIDEIntf, ProjectIntf;
 +
 
 +
procedure ListProjectUnits;
 +
var
 +
  LazProject: TLazProject;
 +
  i: Integer;
 +
begin
 +
  LazProject:=LazarusIDE.ActiveProject;
 +
  if LazProject<>nil then
 +
  begin
 +
    Units:=LazarusIDE.FindUnitsOfOwner(LazProject,[fuooListed,fuooUsed]); // 添加 fuooPackages 以包含包中的单位
 +
    try
 +
      for i:=0 to Units.Count-1 do
 +
        debugln('Filename=',Units[i]);
 +
    finally
 +
      Units.Free;
 +
    end;
 +
  end;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
== 项目的 .lpr、.lpi .lps 文件 ==
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
uses LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
+
uses
 +
  LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
 
var
 
var
 
   LazProject: TLazProject;
 
   LazProject: TLazProject;
 
begin
 
begin
 
   LazProject:=LazarusIDE.ActiveProject;
 
   LazProject:=LazarusIDE.ActiveProject;
   // every project has a .lpi file:
+
   // 每个项目都有一个 .lpi 文件:
 
   DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);
 
   DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);
  
   // if the project session information is stored in a separate .lps file:
+
   // 如果项目会话信息存储在单独的 .lps 文件中:
 
   if LazProject.SessionStorage<>pssNone then
 
   if LazProject.SessionStorage<>pssNone then
 
     DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);
 
     DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);
  
   // If the project has a .lpr file it is the main source file:
+
   // 如果项目有 .lpr 文件,那么它是主要的源文件:
 
   if (LazProject.MainFile<>nil)
 
   if (LazProject.MainFile<>nil)
 
   and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
 
   and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
Line 366: Line 753:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==The executable / target file name of a project==
+
== 项目的可执行文件 / 目标文件名 ==
  
There is a macro $(TargetFile), which can be used in paths and external tools.
+
有一个宏 $(TargetFile),可用于路径和外部工具。您可以在代码中查询宏:
You can query the macro in code:
 
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses MacroIntf;
 
uses MacroIntf;
  
Line 382: Line 768:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
See here for more macros: [[IDE Macros in paths and filenames]].
+
有关更多宏的信息,请参阅此处:[[IDE Macros in paths and filenames]]
 
 
==Add your own project type==
 
  
You can add items to the 'New ...' dialog:
+
== 添加您自己的项目类型 ==
  
*See the unit ProjectIntf of the package IDEIntf.
+
您可以在“新建 ...”对话框中添加项目:
*See the [[Project Templates]]
 
  
 +
* 请参阅 IDEIntf 包的单元 ProjectIntf。
 +
* 请参阅项目模板
  
==Add your own file type==
+
== 添加您自己的文件类型 ==
  
You can add items to the 'New ...' dialog:
+
您可以在“新建 ...”对话框中添加项目:
  
*See the unit ProjectIntf of the package IDEIntf.
+
* 请参阅 IDEIntf 包的单元 ProjectIntf。
*Choose a base class TFileDescPascalUnit for normal units or TFileDescPascalUnitWithResource for a new form/datamodule.
+
* 为普通单元选择基类 TFileDescPascalUnit,为新表单 / 数据模块选择 TFileDescPascalUnitWithResource。
  
===Add a new file type===
+
== 添加新文件类型 ==
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses ProjectIntf;
 
uses ProjectIntf;
 
...
 
...
Line 428: Line 813:
 
begin
 
begin
 
   inherited Create;
 
   inherited Create;
   Name:='MyText'; // do not translate this
+
   Name:='MyText'; // 不要翻译这个
 
   DefaultFilename:='text.txt';
 
   DefaultFilename:='text.txt';
 
   AddToProject:=false;
 
   AddToProject:=false;
Line 435: Line 820:
 
function TFileDescText.GetLocalizedName: string;
 
function TFileDescText.GetLocalizedName: string;
 
begin
 
begin
   Result:='My Text'; // replace this with a resourcestring
+
   Result:='My Text'; // resourcestring 替换它
 
end;
 
end;
  
Line 444: Line 829:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Add a new form type===
+
=== 添加新表单类型 ===
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses ProjectIntf;
 
uses ProjectIntf;
  
Line 473: Line 858:
 
begin
 
begin
 
   inherited Create;
 
   inherited Create;
   Name:='MyForm'; // do not translate this
+
   Name:='MyForm'; // 不要翻译这个
 
   ResourceClass:=TMyForm;
 
   ResourceClass:=TMyForm;
 
   UseCreateFormStatements:=true;
 
   UseCreateFormStatements:=true;
Line 485: Line 870:
 
function TFileDescPascalUnitWithForm.GetLocalizedName: string;
 
function TFileDescPascalUnitWithForm.GetLocalizedName: string;
 
begin
 
begin
   Result:='MyForm'; // replace this with a resourcestring
+
   Result:='MyForm'; // resourcestring 替换它
 
end;
 
end;
  
Line 494: Line 879:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=Packages=
+
== 以 lpi/lps 存储自定义值 ==
 +
 
 +
您可以通过 CustomData 和 CustomSessionData 中的键值对存储您自己的数据。
 +
  LazarusIDE.ActiveProject.CustomData['Name'] := Value ; // 存储在 lpi-x-platform 中,共享数据
 +
  LazarusIDE.ActiveProject.CustomSessionData['Name'] := Value ; // 存储在 lps 中 - 只有本机
 +
 
 +
= 包 =
  
==Search in all packages==
+
您可以使用 Packages 执行各种操作:
  
Iterate all packages loaded in the IDE (since 0.9.29).
+
== 搜索所有包 ==
  
<syntaxhighlight>
+
迭代 IDE 中加载的所有包(自 0.9.29 起)。
uses PackageIntf;
+
 
 +
<syntaxhighlight lang=pascal>uses PackageIntf;
 
...
 
...
 
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
 
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
Line 507: Line 899:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Search a package with a name==
+
== 搜索包含名称的包 ==
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses PackageIntf;
 
uses PackageIntf;
 
...
 
...
Line 516: Line 908:
 
begin
 
begin
 
   Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
 
   Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
   if Pkg<>nil then  
+
   if Pkg<>nil then
 
     ...
 
     ...
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.
+
注意:FindPackageWithName 不会打开包编辑器。为此使用 DoOpenPackageWithName。
 +
 
 +
== 获取已安装软件包的 lpk 文件名 ==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses PackageIntf;
 +
...
 +
var
 +
  Pkg: TIDEPackage;
 +
begin
 +
  Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
 +
  if Pkg<>nil then
 +
    LPKFilename:=Pkg.Filename;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
== 安装包 ==
 +
 
 +
注意:仅安装带有 IDE 插件的软件包。安装其他软件包可能会使 IDE 不稳定。
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses PackageIntf, contnrs;
 +
...
 +
  PkgList:=TObjectList.create(true);
 +
  try
 +
    Pkg:=TLazPackageID.Create;
 +
    Pkg.Name:='Cody';
 +
    PkgList.Add(Pkg);
 +
    // 检查 IDE 是否可以找到 cody.lpk 和所有依赖项
 +
    // 如果看起来很奇怪,IDE 会提示一些警告 / 确认。
 +
    if not PackageEditingInterface.CheckInstallPackageList(PkgList,[]) then
 +
      exit;
 +
    // 在这个例子中我们已经检查过,所以跳过警告并重建 IDE
 +
    if PackageEditingInterface.InstallPackages(PkgList,[piiifSkipChecks,piiifRebuildIDE])<>mrOK then
 +
      exit;
 +
  finally
 +
    PkgList.Free;
 +
  end;
 +
</syntaxhighlight>
 +
 
 +
== 打开包文件(lpk) ==
  
=Windows=
+
<syntaxhighlight lang=pascal>
 +
uses PackageIntf, FileUtil;
 +
...
 +
var
 +
  pkg: TIDEPackage;
 +
begin
 +
  if PackageEditingInterface.DoOpenPackageFile(LPKFilename,[pofAddToRecent],false)<>mrOk then
 +
    exit;
 +
  Pkg:=PackageEditingInterface.FindPackageWithName(ExtractFilenameOnly(LPKFilename));
 +
  ...
 +
end;
 +
</syntaxhighlight>
  
There are basically four types of IDE windows.
+
{{Warning| IDE 会在空闲时自动关闭未使用的软件包。永远不要引用 TIDEPackage。}}
  
*the main IDE bar is the Application.MainForm. It is always present.
+
== 找到一个单元的包裹 ==
*floating/dockable windows like the Source Editors, the Object Inspectors and Messages.
 
*the modal forms, like the find dialog, options dialogs and questions.
 
*hints and completion forms
 
  
== Adding a new dockable IDE window ==
+
你有一个单元的文件名,例如 '/home/user/unit.pas',你想知道它属于哪个包和项目使用这个:
  
What is a dockable IDE window:
+
<syntaxhighlight lang=pascal>
Windows like the Source Editor or the Object Inspector are floating windows, that can be docked if a docking package is installed, and its state, position and size is stored and restored on next IDE start. In order to restore a window the IDE needs a creator as defined in the unit '''IDEWindowIntf''' of the package IDEIntf. Each dockable window must have a unique name. Do not use generic names like 'FileBrowser' because this will easily clash with other packages. And don't use short names like 'XYZ', because the creator is responsible for all forms beginning with this name.
+
uses Classes, PackageIntf, ProjectIntf;
 +
...
 +
var
 +
  Owners: TFPList;
 +
  i: Integer;
 +
  o: TObject;
 +
begin
 +
  Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',[]);
 +
  if Owners=nil then begin
 +
    // 单元与项目 / 包没有直接关联
 +
    // 也许单元出于某种原因没有添加,但可以访问
 +
    // 搜索所有项目 / 包的所有单元路径
 +
    // 注意:这会导致错误命中
 +
    Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',
 +
      [piosfExcludeOwned,piosfIncludeSourceDirectories]);
 +
  end;
 +
  if Owners=nil then exit;
 +
  try
 +
    for i:=0 to Owners.Count-1 do begin
 +
      o:=TObject(Owners[i]);
 +
      if o is TIDEPackage then begin
 +
        writeln('Owner is package ',TIDEPackage(o).Name);
 +
      end else if o is TLazProject then begin
 +
        writeln('Owner is project ',TLazProject(o).ProjectInfoFile);
 +
      end;
 +
    end;
 +
  finally
 +
    Owners.Free;
 +
  end;
 +
</syntaxhighlight>
  
===How to register a dockable IDE window===
+
== 查找项目 / 包所需的包 ==
  
Remember to choose a long unique name that is a valid pascal identifier. Your window can have any caption you want.
+
您有一个项目或包,并且您想知道需要哪个包,使用:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 +
uses Classes, PackageIntf, ProjectIntf;
 +
...
 +
var
 +
  lPkgList: TFPList;
 +
  i: Integer;
 +
begin
 +
  PackageEditingInterface.GetRequiredPackages(LazarusIDE.ActiveProject, lPkgList, [pirNotRecursive]);
 +
  try
 +
    for i := 0 to lPkgList.Count - 1 do begin
 +
      writeln('required package is ' + TIDEPackage(lPkgList[i]).Filename);
 +
    end;
 +
  finally
 +
    lPkgList.Free;
 +
  end;
 +
...
 +
</syntaxhighlight>
 +
 
 +
= 窗口(Windows) =
 +
 
 +
基本上有四种类型的 IDE 窗口。
 +
* 主 IDE 栏是 Application.MainForm。它始终存在。
 +
* 浮动 / 可停靠窗口,如源代码编辑器,对象检查器和消息。
 +
* 模态表单,如查找对话框,选项对话框和问题。
 +
* 提示和完成表格
 +
 
 +
== 添加新的可停靠 IDE 窗口 ==
 +
 
 +
什么是可停靠的 IDE 窗口:像源代码编辑器或对象检查器这样的 Windows 是浮动窗口,如果安装了扩展包,则可以停靠它,并在下次 IDE 启动时存储和恢复其状态,位置和大小。为了恢复窗口,IDE 需要在 IDEIntf 包的单元 IDEWindowIntf 中定义的创建者。每个可停靠窗口必须具有唯一名称。不要使用像 'FileBrowser' 这样的通用名称,因为这很容易与其他包冲突。并且不要使用像“XYZ”这样的短名称,因为创建者负责以此名称开头的所有表单。
 +
 
 +
=== 如何注册可停靠的 IDE 窗口 ===
 +
 
 +
请记住选择一个有效的 pascal 标识符的长唯一名称。您的窗口可以包含您想要的任何标题。
 +
 
 +
<syntaxhighlight lang=pascal>
 
uses SysUtils, IDEWindowIntf;
 
uses SysUtils, IDEWindowIntf;
 
...
 
...
var MyIDEWindow: TMyIDEWindow = nil;  
+
var MyIDEWindow: TMyIDEWindow = nil;
  
 
procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
 
procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
 
begin
 
begin
   // check the name
+
   // 检查名称
 
   if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
 
   if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
   // create the form if not already done and disable autosizing
+
   // 如果尚未创建表单并禁用自动调整大小
 
   IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
 
   IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
 
   ... init the window ...
 
   ... init the window ...
Line 559: Line 1,062:
 
begin
 
begin
 
   IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
 
   IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
   // the default boundsrect of the form is:
+
   // 表单的默认边界是:
   // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
+
   // Left=100, Top=Screen.Height div 2, Width=300, Height=Screen.Height div 5
   // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
+
   // IDE 需要此窗口的实例时,它会调用 CreateMyIDEWindow 过程。
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Showing an IDE window===
+
=== 显示 IDE 窗口 ===
  
'''Do not use Show'''. Use:
+
不要使用 Show。使用:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
IDEWindowCreators.ShowForm(MyIDEWindow,false);
 
IDEWindowCreators.ShowForm(MyIDEWindow,false);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This will work with docking. The docking system might wrap the form into a docking site. The BringToFront parameter tells the docking system to make the form and all its parent sites visible and bring the top level site to the front.
+
这将适用于对接。对接系统可以将表格包裹到对接站点中。BringToFront 参数告诉对接系统使表单及其所有父站点可见,并将顶层站点放在前面。
 +
 
 +
=== 有关 IDEWindowCreators 和 SimpleLayoutStorage 的注释 ===
  
===Notes about IDEWindowCreators and SimpleLayoutStorage===
+
IDEWindowCreators.SimpleLayoutStorage 只存储曾经打开过的所有表单的 BoundsRect 和 WindowState。如果没有安装 dockmaster,它将用作后备。即使安装了 DockMaster,它也会存储状态,因此在卸载 dockmaster 时,将恢复表单边界。
  
The IDEWindowCreators.SimpleLayoutStorage simply stores the BoundsRect and WindowState of all forms that were once opened. It is used as fallback if no dockmaster is installed. It stores the state even if a DockMaster is installed, so that when the dockmaster is uninstalled the forms bounds are restored.
+
所有可停靠表单都使用 IDEWindowCreators 来注册自己并显示表单。在显示表单时,Creator 会检查是否安装了 IDEDockMaster,并将显示委托给它。如果没有安装 IDEDockMaster,它只显示表单。IDEDockMaster 可以使用 IDEWindowCreators 中的信息按名称创建表单,并在第一次显示表单时了解放置表单的位置。有关更多详细信息,请参阅包 AnchorDockingDsgn 和 EasyDockMgDsgn。
  
The IDEWindowCreators is used by all dockable forms to register themselves and to show forms. When showing a form the Creator checks if a IDEDockMaster is installed and will delegate the showing to it. If no IDEDockMaster is installed it simply shows the form. The IDEDockMaster can use the information in the IDEWindowCreators to create forms by names and get an idea where to place a form when showing it for the first time. For more details see the packages AnchorDockingDsgn and EasyDockMgDsgn.
+
= 代码工具 =
  
=CodeTools=
+
CodeTools 是一个包,提供了大量的功能来解析、搜索和更改 pascal 源代码以及其他语言的一些基本功能。您也可以在没有 IDE 的情况下使用它们。在 IDE 中使用它们之前,建议您首先阅读 [[Codetools]]。
  
Before using the codetools you should commit the current changes of the source editor to the codetool buffers:
+
在调用 IDE 中的任何 CodeTools 函数之前,您应该将源代码编辑器的当前更改提交到 CodeTools 缓冲区:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses LazIDEIntf;
 
uses LazIDEIntf;
 
...
 
...
   // save changes in source editor to codetools
+
   // 将源代码编辑器中的更改保存到 codetools
   LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors
+
   LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1:提交所有源代码编辑器
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Adding a resource directive to a file==
+
== 将资源指令添加到文件中 ==
  
This adds a {$R example.res} to a pascal unit:
+
这为 pascal 单元添加 {$R example.res}
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
procedure AddResourceDirectiveToPascalSource(const Filename: string);
 
procedure AddResourceDirectiveToPascalSource(const Filename: string);
 
var
 
var
Line 602: Line 1,107:
 
   CodeBuf: TCodeBuffer;
 
   CodeBuf: TCodeBuffer;
 
begin
 
begin
   // make sure the filename is trimmed and contains a full path
+
   // 确保修剪文件名并包含完整路径
 
   ExpandedFilename:=CleanAndExpandFilename(Filename);
 
   ExpandedFilename:=CleanAndExpandFilename(Filename);
 
    
 
    
   // save changes in source editor to codetools
+
   // 将源代码编辑器中的更改保存到 codetools
 
   LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);
 
   LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);
  
   // load the file
+
   // 加载文件
 
   CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);
 
   CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);
  
   // add the resource directive
+
   // 添加资源指令
 
   if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
 
   if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
 
     LazarusIDE.DoJumpToCodeToolBossError;
 
     LazarusIDE.DoJumpToCodeToolBossError;
Line 617: Line 1,122:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The codetools provides also functions like FindResourceDirective and RemoveDirective.
+
codetools 还提供 FindResourceDirective RemoveDirective 等函数。
 +
 
 +
== 获取单元和包含文件的搜索路径 ==
 +
 
 +
IDE 中有许多不同的搜索路径来自项目,包,fpc 和 lazarus 目录,并且有许多类型的路径:在解析宏之前或之后,有或没有继承的搜索路径,作为相对路径或绝对路径。目录中的所有文件共享同一组搜索路径。您可以通过询问 codetools 来完全解析每个目录的搜索路径:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
uses CodeToolManager;
 +
...
 +
 
 +
Dir:=''; // 空目录用于新文件,并且具有与项目目录相同的设置
 +
 
 +
// 获取包含文件的搜索路径:
 +
Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);
 +
 
 +
// 获取单元的搜索路径:
 +
// 此搜索路径将传递给编译器。
 +
// 它包含包输出目录,但不包含包源目录。
 +
Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);
 +
 
 +
// 只能为 IDE 提供额外的单元搜索路径(不传递给编译器)
 +
Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);
 +
 
 +
// 完整的搜索路径还包含单元的所有包源路径:
 +
Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);
 +
</syntaxhighlight>
 +
 
 +
= 源代码编辑 =
  
=Help=
+
== 激活源代码编辑器 ==
  
==Adding help for sources==
+
<syntaxhighlight lang=pascal>
 +
uses SrcEditorIntf;
 +
...
 +
Editor:=SourceEditorManagerIntf.ActiveEditor;
 +
if Editor=nil then exit;
 +
Filename:=Editor.FileName;
 +
ScreenPos:=Editor.CursorScreenXY;
 +
TextPos:=Editor.CursorTextXY;
 +
</syntaxhighlight>
  
First create a THelpDatabase:
+
= SynEdit =
  HelpDB:=TFPDocHTMLHelpDatabase(
 
    HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
 
                                            TFPDocHTMLHelpDatabase,true));
 
  HelpDB.DefaultBaseURL:='http://your.help.org/';
 
  
  FPDocNode:=THelpNode.CreateURL(HelpDB,
+
== 获取 TSynEdit 的设置 ==
                  'Package1 - A new package',
 
                  'file://index.html');
 
  HelpDB.TOCNode:=THelpNode.Create(HelpDB,FPDocNode);// once as TOC
 
  DirectoryItem:=THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
 
                                  '*.pp;*.pas',false);// and once as normal page
 
  HelpDB.RegisterItem(DirectoryItem);
 
  
==Adding lines to the messages window==
+
当您使用 TSynEdit 进行对话时,如果要使用与源代码编辑器相同的字体和设置,请使用:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 +
uses SrcEditorIntf;
 +
...
 +
SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);
 +
</syntaxhighlight>
 +
 
 +
== 获取 SynEdit 的高亮设置 ==
 +
 
 +
如果您有一个带有高亮的 TSynEdit 的对话框,并且您希望使用与源代码编辑器高亮相同的颜色来使用此语言:
 +
 
 +
使 <syntaxhighlight lang=pascal>
 +
uses SrcEditorIntf;
 +
...
 +
SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);
 +
</syntaxhighlight>
 +
 
 +
请参阅示例:[http://svn.freepascal.org/svn/lazarus/trunk/components/sqldb/sqlstringspropertyeditordlg.pas SQLStringsPropertyEditorDlg] 单元中的 TSQLStringsPropertyEditorDlg.Create。
 +
 
 +
= 帮助 =
 +
 
 +
== 添加源的帮助 ==
 +
 
 +
首先创建一个 THelpDatabase:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
HelpDB := TFPDocHTMLHelpDatabase(
 +
  HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
 +
  TFPDocHTMLHelpDatabase,true));
 +
HelpDB.DefaultBaseURL := 'http://your.help.org/';
 +
 
 +
FPDocNode := THelpNode.CreateURL(HelpDB,
 +
  'Package1 - A new package',
 +
  'file://index.html');
 +
HelpDB.TOCNode := THelpNode.Create(HelpDB,FPDocNode);// 一次作为 TOC
 +
DirectoryItem := THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
 +
  '*.pp;*.pas',false);// 一次作为正常页面
 +
HelpDB.RegisterItem(DirectoryItem);
 +
</syntaxhighlight>
 +
 
 +
== 在消息窗口中添加行 ==
 +
 
 +
<syntaxhighlight lang=pascal>
 
unit IDEMsgIntf;
 
unit IDEMsgIntf;
 
...
 
...
Line 650: Line 1,221:
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
= IDE 选项框架 =
 +
 +
== 打开特定框架中的选项 ==
 +
 +
<syntaxhighlight lang=pascal>
 +
uses LazIDEIntf;
 +
...
 +
LazarusIDE.DoOpenIDEOptions(TYourIDEOptionsFrame);
 +
</syntaxhighlight>
 +
 +
= 其它 =
 +
 +
== 添加宏 ==
 +
 +
您可以添加自己的 IDE 宏:
 +
 +
<syntaxhighlight lang=pascal>
 +
uses MacroIntf, MacroDefIntf;
 +
 +
procedure Register;
 +
begin
 +
  // 注册具有固定值的宏:
 +
  IDEMacros.Add(TTransferMacro.Create('MyMacro1','Value','Description',nil,[]));
 +
end;
 +
 +
// 使用动态值注册宏需要一个对象的方法
 +
procedure TForm1.Init;
 +
begin
 +
  IDEMacros.Add(TTransferMacro.Create('MyMacro','Value','Description',@OnResolveMyMacro,[]));
 +
end;
 +
 +
function TForm1.OnResolveMyMacro(const s: string; const Data: PtrInt;
 +
  var Abort: boolean): string;
 +
// s 是参数。例如 $MyMacro(parameter)
 +
// 数据是查询实例。可以是零。
 +
// 如果有错误设置 Abort 为 true。
 +
// 结果字符串可以包含宏。例如 Result:='$(FPCVer)/$(TargetOS)';
 +
begin
 +
  Result:='MyValue';
 +
end;
 +
</syntaxhighlight>
 +
 +
== 调试 IDE ==
 +
 +
* 像往常一样编译 IDE,例如通过 Tools/Build Lazarus。确保设置调试标志,如 -g 或 -gw2。最好是使用配置文件“Debug IDE”,其中包含许多用于调试的标志。
 +
* 打开 ide \ lazarus.lpi 项目。您无法直接编译此项目。
 +
* 根据需要设置断点等运行项目。
 +
 +
== 另请参见 ==
 +
 +
* [[Creating IDE Help]]
 +
* [[Help protocol]]
 +
* [[How To Write Lazarus Component]]

Latest revision as of 23:26, 14 February 2020

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

扩展 IDE

概述

Lazarus IDE 支持以下几种类型的插件:

组件(Components)
这些是“组件面板”上的项目。例如 TButton(可以在“标准”页面上找到)可用于创建按钮。
组件编辑器(Component Editors)
当你在“界面设计器(Designer)”中双击一个组件时(比如“TMainMenu”组件),将调用组件编辑器(比如“菜单编辑器”)。组件编辑器还可以在“界面设计器”的右键菜单中添加一个额外的菜单项(右键单击“界面设计器”中的组件时可以访问)。
属性编辑器(Property Editors)
“对象查看器”的列表中的每一行都使用属性编辑器来设置或更改该属性的值。
专家(Experts)
专家包含所有其他插件类型。他们可能会注册新的菜单项,注册快捷方式或扩展其他 IDE 功能。


有两种方式可以将自己的插件添加到 Lazarus IDE:

  1. 编写一个包,安装它并在包单元的“Register”过程中注册你的插件。
  2. 扩展 Lazarus 代码,并将您的 svn diff 发送到 Lazarus 邮件列表。

包和 Makefile 依赖项

如果添加新的包依赖项,则必须执行以下步骤。

  • 创建 / 通过包编辑器更新包的 MakeFile/ 更多 .../ 创建 Makefile。
  • 修复所有依赖项。请参阅 tools/updatemakefiles.lpi

菜单项

您可以添加新菜单项,隐藏或移动现有菜单项并添加子菜单、菜单区(菜单区是整个菜单中的一块区域,它可以是一个包含子菜单的菜单项,也可以是由分隔符分隔出来的一块区域。如果菜单区中没有子项,则该菜单区将不可见。比如 itmFileNew 就是一个用分隔符分隔的菜单区,它里面包含了“新建单元”、“新建窗体”、“新建 ...” 等菜单项,itmFileRecentOpen 也是一个菜单区,它只是一个菜单项,包含“最近打开的文件”子菜单)。对于您自己的窗体,您可以注册 TMainMenu/TPopupMenu,以便其它包可以进一步扩展您的窗体。IDEIntf 包的 MenuIntf 单元包含菜单的所有注册函数和 IDE 本身的许多标准菜单项。

添加菜单项

像“查看 -> 对象查看器”这样的单个菜单项称为 TIDEMenuCommand。您可以使用 RegisterIDEMenuCommand 创建一个,它有两个带有大量参数的格式:

function RegisterIDEMenuCommand(
    Parent: TIDEMenuSection;
    const Name, Caption: string;
    const OnClickMethod: TNotifyEvent = nil;
    const OnClickProc: TNotifyProcedure = nil;
    const Command: TIDECommand = nil;
    const ResourceName: String = ''
    ): TIDEMenuCommand; overload;

function RegisterIDEMenuCommand(
    const Path, Name, Caption: string;
    const OnClickMethod: TNotifyEvent = nil;
    const OnClickProc: TNotifyProcedure = nil;
    const Command: TIDECommand = nil;
    const ResourceName: String = ''
    ): TIDEMenuCommand; overload;

这两者之间的区别仅在于指定父菜单区的方式。您可以通过 Path 参数直接或间接地给出菜单区。MenuIntf 单元中可以找到许多标准菜单区。例如 mnuTools 菜单区,它是主 IDE 栏的 Tools 菜单。它包含一个子菜单区 itmSecondaryTools,用于第三方工具的推荐菜单区。

下面的代码注册了一个新的菜单命令,菜单名(Name)为“MyTool”,菜单标题(Caption)为“我的工具(&T)”,点击后执行 StartMyTool 过程:

procedure StartMyTool;
begin
  ... 点击菜单项后要执行的代码 ...
end;

procedure Register;
begin
  RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', ' 我的工具(&T)', nil, @StartMyTool);
end;

如果要调用方法而不是过程,请使用 OnClickMethod 参数。

此菜单项没有快捷键。如果需要快捷键,则必须创建一个 TIDECommand 对象并将其传递给 Command 参数。例如:

uses
  ... Classes, LCLType, Dialogs, IDECommands, MenuIntf;

...

procedure StartMyTool(Sender :TObject);
begin
  ShowMessage('Hello World!');
end;

procedure Register;
var
  Key: TIDEShortCut;
  Cat: TIDECommandCategory;
  CmdMyTool: TIDECommand;
begin
  // 注册 IDE 快捷键(Ctrl+Alt+Shift+T)和菜单项
  Key := IDEShortCut(VK_T,[ssCtrl,ssAlt,ssShift],VK_UNKNOWN,[]);
  Cat := IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
  CmdMyTool := RegisterIDECommand(Cat, 'StartMyTool', ' 启动我的工具 ', Key, nil, @StartMyTool);
  RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', ' 我的工具 (&T)', nil, nil, CmdMyTool);
end;

快捷键

所有快捷键都在 IDECommandList 中注册。下面是一个示例,将查找所有使用 Ctrl-D 作为快捷键的命令:

uses
  Classes, SysUtils, Dialogs, LCLType, LCLProc, IDECommands, MenuIntf;

// 列出所有使用 Ctrl-D 的 IDE 命令
procedure ListCtrlD;
var
  i        :integer;
  Cmd      :TIDECommand;
  Commands :TFPList;
begin
  Commands := IDECommandList.FindCommandsByShortCut(IDEShortCut(VK_D, [ssCtrl], VK_UNKNOWN, []));
  try
    for i := 0 to Commands.Count - 1 do
    begin
      Cmd := TIDECommand(Commands[i]);
      ShowMessage(format('Cmd: %s, A = %s - %d, %s - %d' + #13#10 + 'B = %s - %d, %s - %d',
      [Cmd.Name,
      dbgs(Cmd.ShortcutA.Shift1),
      Cmd.ShortcutA.Key1,
      dbgs(Cmd.ShortcutA.Shift2),
      Cmd.ShortcutA.Key2,
      dbgs(Cmd.ShortcutB.Shift1),
      Cmd.ShortcutB.Key1,
      dbgs(Cmd.ShortcutB.Shift2),
      Cmd.ShortcutB.Key2]));
    end;
  finally
    Commands.Free;
  end;
end;

配置文件

加载和保存配置

IDE 的所有配置文件都以 xml 格式存储在一个目录中(主配置目录)。包也可以在那里添加自己的配置文件。可以使用下面的方式读取“主配置目录”:

uses LazIDEIntf;
...
  Directory := LazarusIDE.GetPrimaryConfigPath;

包可以创建自己的 xml 文件:

uses
  ..., LCLProc, BaseIDEIntf, LazConfigStorage;

const
  Version = 1;
var
  Config: TConfigStorage;
  SomeValue: String;
  SomeInteger: Integer;
  SomeBoolean: Boolean;
  FileVersion: LongInt;
begin
  SomeValue:='Default';
  SomeInteger:=3;
  SomeBoolean:=true;

  // 保存配置
  try
    Config:=GetIDEConfigStorage('mysettings.xml',false);
    try
      // 存储版本号,以便将来的扩展可以处理旧的配置文件
      Config.SetDeleteValue('Path/To/The/Version',Version,0);
      // 存储字符串变量 “someValue”
      // 如果 someValue 的值和默认值相同,则不会存储该项
      // 因此只有与默认值不同的项才会被存储
      // 这样能使 XML 保持简短,并且便于在将来改变默认值。
      Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
      Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
      Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
      // 还有很多 SetDeleteValue 的重载,通过 Ctrl+Space(代码提示的快捷键)查看它们。
    finally
      Config.Free;
    end;

  except
    on E: Exception do begin
      DebugLn(['Saving mysettings.xml failed: ',E.Message]);
    end;
  end;

  // 加载配置
  try
    Config:=GetIDEConfigStorage('mysettings.xml',true);
    try
      // 读取配置的版本
      FileVersion:=Config.GetValue('Path/To/The/Version',0);
      // 读取字符串变量“SomeValue”。如果该项不存在,则使用默认值。
      SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
      SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
      SomeBoolean:=Config.GetValue('Path/To/Some/Boolean',true);
    finally
      Config.Free;
    end;
  except
    on E: Exception do begin
      DebugLn(['Loading mysettings.xml failed: ',E.Message]);
    end;
  end;
end;

注意:

  • 每个 IDE 插件都应该使用自己的配置文件。特别是您不能使用标准 IDE 选项文件,如 editoroptions.xml 或 environmentoptions.xml。
  • GetIDEConfigStorage 在加载时,会检查文件是否存在,如果不存在,它将从辅助配置目录复制模板。
  • 从 0.9.31 开始,您可以使用此函数在主配置目录之外加载文件。

组件

扩展现有组件

来源:[1] 定制现有的 LCL 组件很简单,例如,如果要添加属性。

使用字段及其相应的 setter 和 getter 方法来添加所需的属性。

然后“手动”实例化它们,给出一个 OWner 组件(以便处理稍后的销毁操作),并设置 Parent 和可能的 Top 和 Left 以及组件在初始化时需要的任何其他默认值。

如果您对自定义组件的调试感到满意,请添加一个 RegisterComponent() 调用以将其加入到 IDE 的组件面板中,并保存您必须“手动”编写的设置代码(查看任一个组件的源代码以了解如何执行此操作)。

这是一个简单的例子:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, StdCtrls;

type
  TEdX = class(TEdit)
  private
    FNewIntProp: integer;
    FNewStrProp: string;
    procedure SetNewIntProp(AValue: integer);
    procedure SetNewStrProp(AValue: string);
  public
    constructor Create(theComponent: TComponent); override;
    property NewIntProp: integer read FNewIntProp write SetNewIntProp;
    property NewStrProp: string read FNewStrProp write SetNewStrProp;
  end;

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    edX: TEdX;
  public
  end;

var
  Form1: TForm1;

implementation

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  edX:= TEdX.Create(Self);
  edX.Parent:= Self;
  edX.Caption:= IntToStr(edX.NewIntProp);
  Caption:= edX.NewStrProp;
end;

{$R *.lfm}

{ TEdX }

procedure TEdX.SetNewIntProp(AValue: integer);
begin
  if FNewIntProp=AValue then Exit;
  FNewIntProp:=AValue;
  // 这里填写自定义代码
end;

procedure TEdX.SetNewStrProp(AValue: string);
begin
  if FNewStrProp=AValue then Exit;
  FNewStrProp:=AValue;
  // 这里填写自定义代码
end;

constructor TEdX.Create(theComponent: TComponent);
begin
  inherited Create(theComponent);
  FNewStrProp:= 'new string property value';
  FNewIntProp:= 99;
end;

end.

编写组件

主要文章:How To Write Lazarus Component

您可以通过包编辑器创建新组件。

创建或打开一个包,单击“添加 -> 新组件”,填写项目: Ancestor Type = TButton, New class name = TMyButton, palette page = Misc, Unit name = mybutton.pas(将创建此文件) , 单位名称 MyButton,然后单击确定。

为组件面板提供一个新组件图标

创建格式为 .bmp、.xpm 或 .png 的图像文件,其名称与组件类相同。例如 tmybutton.png 并将其保存在包源目录中。图像可以由任何图形程序(例如 gimp)创建,并且不应大于 24x24 像素。然后使用 lazres 工具将图像转换为资源文件,该工具可以在 lazarus/tools 目录中找到:

~/lazarus/tools/lazres mybutton.lrs tmybutton.png
# note: Laz trunk/1.4+? uses .res resource files instead of .lrs files:
~/lazarus/tools/lazres mybutton.res tmybutton.png

这将创建一个 pascal include 文件,该文件在 mybutton.pas 的初始化部分中使用:

initialization
  {$I mybutton.lrs}
  // 或在较新的 Lazarus 版本中 - to docheck 语法
  {$R mybutton.res}

安装包。

隐藏组件面板中的组件

Package IDEIntf, unit componentreg:

  IDEComponentPalette.FindComponent('TButton').Visible:=false;

编写组件编辑器

组件编辑器处理诸如双击设计器中的组件或右键单击组件时添加菜单项之类的操作。编写组件编辑器很简单。有关大量示例,请参阅 IDEIntf 包的 componenteditors.pas 单元。例如,双击时显示编辑器,请参阅 TCheckListBoxComponentEditor。

编写属性编辑器

“属性查看器”中的每种属性(整数、字符串、TFont、...)都需要属性编辑器。如果组件具有字符串类型的属性 Password,则可以为特定组件类定义属性编辑器,并使用字符串类型的名为 Password 的属性。在 IDEIntf 的 propedits.pp 单元中包含 IDE 本身使用的许多标准属性编辑器。例如 TStringPropertyEditor 是所有字符串的默认属性编辑器,而 TComponentNamePropertyEditor 更有针对性,它只处理 TComponent.Name。请参阅示例中的单元文件。

在 Object Inspector 中隐藏属性

有一个特殊的属性编辑器用于隐藏:

  RegisterPropertyEditor(TypeInfo(TAnchorSide), TControl, 'AnchorSideLeft', THiddenPropertyEditor);

找出属性使用的属性编辑器

uses Classes, TypInfo;
var
  Instance: TPersistent; // 你的实例
  PropInfo: PPropInfo;
  EdClass: TPropertyEditorClass;
begin
  PropInfo:=GetPropInfo(Instance.ClassType,'Name'); // 将 Name 替换为您要搜索的属性名称
  GetEditorClass(PropInfo, Instance);
end;

窗体

IDE 允许轻松创建窗体和框架的后代。这些窗体可以包含新的 published 组件和新的 published 方法。但是,当您添加新的 published 属性时,无法通过“对象查看器”访问它们。它们仅在运行时工作。IDE 中的方法是伪造的,无法执行。IDE 可以创建组件,因为它们的类已在组件面板中注册。你的新属性仅存在于源代码中,IDE 还不能伪造它们。这就是你必须注册新类的原因。这意味着:

  • 创建设计时包(或扩展现有包)
  • 您的窗体必须在此包的单元中
...
interface

uses FormEditingIntf, Forms;

type
  TYourForm = class(TForm)
  private
    FYourProperty: integer;
  published
    property YourProperty: integer read FYourProperty write FYourProperty;
  end;

procedure Register;

implementation

procedure Register;
begin
 FormEditingHook.RegisterDesignerBaseClass(TYourForm);
end;
...
  • 检查包编辑器中本单元的“Register”。
  • 在 IDE 中安装包

界面设计器(Designer)

编写一个设计器中间层

标准设计器允许可视化编辑 LCL 控件,而所有其它控件都显示为图标(如 TOpenDialog 或 TDataSource)。要可视地编辑非 LCL 控件,您需要创建一个 设计器中间层 。这可用于设计网页,UML 图或其它小部件集,如 fpGUI。在 examples/designnonlcl/ 中有一个完整的例子。

  • 安装示例包 examples/designnonlcl/notlcldesigner.lpk 并重新启动 IDE。这将为 TMyWidget 组件注册设计器中间层,并将新组件 TMyButton,TMyGroupBox 添加到组件面板中。
  • 打开示例项目 examples/designnonlcl/project/NonLCL1.lpi。
  • 打开 unit1.pas 并切换到设计器界面(F12)。您应该看到组件为红色矩形,可以像 LCL 控件一样选择,移动和调整大小。

创建一个新的唯一组件名称

uses FormEditingIntf;

...

NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);

// 或者如果您需要在创建组件之前创建名称:

NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
// ComponentClassName 将用作前缀,没有前导 T.
// OwnerComponent 是新组件的新所有者。

在设计器 / 对象检查器中选择一个组件

uses propedits;
..
GlobalDesignHook.SelectOnlyThis(AComponent);

获取单元,设计器,文件的形式

uses ProjectIntf, LazIDEIntf, ComponentEditors;
...
// 打开文件
if LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent])<>mrOk then exit;
// 获取文件界面
aFile:=LazarusIDE.ActiveProject.FindFile(Filename,[]);
// 获取设计器
aDesigner:=TComponentEditorDesigner(LazarusIDE.GetDesignerWithProjectFile(AFile,true));
if aDesigner=nil then
  exit; // 这个单元没有资源(形式,框架,数据模块等)
// 获取表单
aForm:=aDesigner.Form;

保存单元,设计器窗口

uses LazIDEIntf;
...
if LazarusIDE.DoSaveEditorFile(Filename,[])<>mrOk then exit;

将设计器窗口保存为 Pascal

  • 使用 FormEditingfIntf 单元的 FormEditingHook.SaveComponentAsPascal 函数。请参阅 examples/pascalstream/CopyAsPasPkg/copyaspasdemounit1.pas

事件处理程序

IDE 中有几个事件,插件可以为其添加自己的处理程序。

设计器活动

在 propedits.pp 中有一个“GlobalDesignHook”对象,它维护了几个用于设计的事件。每个事件都会调用一个处理程序列表。IDE 添加了默认处理程序。您可以使用 AddHandlerXXX 和 RemoveHandlerXXX 方法添加自己的处理程序。它们将在默认处理程序之前调用。

例子:

  • 添加处理程序(通常在对象的构造函数中完成):
 GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);
  • 删除处理程序:
 GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);
  • 您可以一次删除所有处理程序。例如,在对象的析构函数中添加此行是个好主意:
 GlobalDesignHook.RemoveAllHandlersForObject(Self);

GlobalDesignHook 的处理程序:

  • 查找根目录
    • ChangeLookupRoot - 当“LookupRoot”发生变化时调用。“LookupRoot”是当前所选组件的所有者对象。通常这是一个 TForm。
  • 方法
    • CreateMethod
    • GetMethodName
    • 的 getMethods
    • MethodExists
    • RenameMethod
    • ShowMethod
    • MethodFromAncestor
    • ChainCall
  • 组件
    • GetComponent
    • GetComponentName
    • GetComponentNames
    • GetRootClassName
    • AddClicked - 在用户选择组件类并单击设计器以添加新组件时调用。您可以更改组件类和父组件。
    • ComponentRenamed - 在重命名组件时调用
  • TPersistent 对象
    • PersistentAdded - 在将新的 TPersistent 添加到 LookupRoot 时调用
    • PersistentDeleting - 在释放 TPersistent 之前调用。
    • DeletePersistent - 由 IDE 调用以删除 TPersistent。
    • GetSelectedPersistents - 获取当前 TPersistent 选择时调用。
    • SetSelectedPersistents - 在设置 TPersistent 的当前选择时调用。
    • GetObject
    • GetObjectName
    • GetObjectNames
  • 修改
    • Modified
    • Revert
    • RefreshPropertyValues
  • 选区
    • SetSelection
    • GetSelection

设计器活动的例子

示例:当放置新组件时,将新组件移动到子组件中

...
type
  TYourDesignerExtension = class
  private
    function AddClicked(ADesigner: TIDesigner;
             MouseDownComponent: TComponent; Button: TMouseButton;
             Shift: TShiftState; X, Y: Integer;
             var AComponentClass: TComponentClass;
             var NewParent: TComponent): boolean of object;
  public
    constructor Create;
    destructor Destroy; override;
  end;

...
constructor TYourDesignerExtension.Create;
begin
  // 注册你的处理程序
  GlobalDesignHook.AddHandlerAddClicked(@AddClicked);
end;

destructor TYourDesignerExtension.Destroy;
begin
  // 删除你的处理程序
  GlobalDesignHook.RemoveHandlerAddClicked(@AddClicked);
end;

function TYourDesignerExtension.AddClicked(ADesigner: TIDesigner;
             MouseDownComponent: TComponent; Button: TMouseButton;
             Shift: TShiftState; X, Y: Integer;
             var AComponentClass: TComponentClass;
             var NewParent: TComponent): boolean of object;
begin
  // 在此示例中,TYourControl 是一个带有 ChildControl 的自定义控件。
  // 每当用户删除 TYourControl 上的控件而不是将新控件添加到 TYourControl 时,新控件将被添加为 ChildControl 的子控件。
  // 已创建
  if MouseDownComponent is TYourControl then
    NewParent:=TYourControl(MouseDownComponent).ChildControl;
  Result:=true;
end;

禁用设计器鼠标处理程序

通常,设计人员会捕获组件的所有鼠标事件。如果您的自定义控件应该处理鼠标事件句柄,您可以设置 csDesignInteractive 控件样式 ,也可以通过控件中的消息 CM_DESIGNHITTEST 对其进行精确控制:

type
  TYourControl = class(TCustomControl)
  protected
    procedure CMDesignHitTest(var Message: TLMessage); message CM_DESIGNHITTEST;
  end;
...
procedure TYourControl.CMDesignHitTest(var Message: TLMessage);
var
  p: TSmallPoint;
  aShiftState: TShiftState;
begin
  aShiftState:=KeysToShiftState(PtrUInt(Message.WParam));
  p:=TSmallPoint(longint(Message.LParam));
  debugln(['TForm1.CMDesignHitTest ShiftState=',dbgs(aShiftState),' x=',p.x,' y=',p.y]);
  if SkipDesignerMouseHandler then
    Message.Result:=1; // 现在设计器调用了正常的 MouseUp、MouseMove 和 MouseDown 方法。
end;

在修改设计器窗体时收到通知

每个设计的 LCL 表格都有一个 TIDesigner 类型的设计器。IDE 将创建在 IDEIntf 单元组件中定义的 TComponentEditorDesigner 类型的设计器。例如:

procedure TYourAddOn.OnDesignerModified(Sender: TObject);
var
  IDEDesigner: TComponentEditorDesigner;
begin
  IDEDesigner:=TComponentEditorDesigner(Sender);
  ...
end;

procedure TYourAddOn.ConnectDesignerForm(Form1: TCustomForm);
var
  IDEDesigner: TComponentEditorDesigner;
begin
  IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
  IDEDesigner.AddHandlerModified(@OnDesignerModified);
end;

项目事件

这些事件在单元 LazIDEIntf 中定义。

  • LazarusIDE.AddHandlerOnProjectClose:在项目关闭之前调用
  • LazarusIDE.AddHandlerOnProjectOpened:在项目完全打开后调用(例如,加载了所有必需的包,在源代码编辑器中打开了单元)
  • LazarusIDE.AddHandlerOnSavingAll:在 IDE 保存所有内容之前调用
  • LazarusIDE.AddHandlerOnSavedAll:在 IDE 保存所有内容后调用
  • LazarusIDE.AddHandlerOnProjectBuilding:在 IDE 构建项目之前调用
  • LazarusIDE.AddHandlerOnProjectDependenciesCompiling:在 IDE 编译项目的包依赖项之前调用
  • LazarusIDE.AddHandlerOnProjectDependenciesCompiled:在 IDE 编译包项目依赖项后调用
  • LazarusIDE.AddHandlerOnProjectBuildingFinished:在 IDE 构建项目后调用(成功与否)(自 1.5 起)

其他 IDE 事件

uses LazIDEIntf;

在包的注册过程中调用这些:

  • LazarusIDE.AddHandlerOnIDERestoreWindows:在 IDE 恢复其窗口时调用(在打开第一个项目之前)
  • LazarusIDE.AddHandlerOnIDEClose:在 IDE 关闭时调用(在 closequery 之后调用,因此不再进行交互)
  • LazarusIDE.AddHandlerOnQuickSyntaxCheck:执行菜单项或快速语法检查的快捷方式时调用
  • LazarusIDE.AddHandlerOnLazarusBuilding:// 在 IDE 构建 Lazarus IDE 之前调用(从 1.5 开始)
  • LazarusIDE.AddHandlerOnLazarusBuildingFinished:// 在 IDE 构建 Lazarus IDE 后调用(自 1.5 起)
  • LazarusIDE.AddHandlerGetFPCFrontEndParams:// 当 IDE 获取 'fpc' 前端工具的参数时调用(自 1.5 起)
  • LazarusIDE.AddHandlerGetFPCFrontEndPath:// 当 IDE 获取 'fpc' 前端工具的路径时调用(从 1.5 开始)
  • LazarusIDE.AddHandlerOnUpdateIDEComponentPalette:?
  • LazarusIDE.AddHandlerOnUpdateComponentPageControl :?
  • LazarusIDE.AddHandlerOnShowDesignerFormOfSource:// 在显示代码编辑器的设计器表单后调用(AEditor 可以是 nil!)(自 1.5 起)
  • LazarusIDE.AddHandlerOnShowSourceOfActiveDesignerForm:// 显示设计器表单代码后调用(自 1.5 起)
  • LazarusIDE.AddHandlerOnChangeToolStatus:// 在 IDEToolStatus 发生变化时调用(例如 itNone-> itBuilder 等)(自 1.5 起)

在您的某个单元的初始化部分中调用它们:

  • AddBootHandler
  • AddBootHandler(libhTransferMacrosCreated, @YourProc):创建 IDEMacros 后调用(自 1.3 起)
  • AddBootHandler(libhEnvironmentOptionsLoaded, @YourProc):加载环境选项后调用(自 1.3 起)

项目

当前的项目

目前的主要项目可以通过 LazarusIDE.ActiveProject 获得。(单元 LazIDEIntf)

当前项目的所有单元

请注意,术语“项目单元”是不明确的。

  • 项目检查器显示 .lpi 文件的列表。该项目可能不会在所有平台上使用所有这些单元,也许开发人员忘记添加所有使用过的单元。
  • 使用过的包中的单元不是项目单元。

项目检查员的所有单元

要遍历项目检查器中列出的所有 pascal 单元,您可以使用例如:

uses
  LCLProc, FileUtil, LazIDEIntf, ProjectIntf;

procedure ListProjectUnits;
var
  LazProject: TLazProject;
  i: Integer;
  LazFile: TLazProjectFile;
begin
  LazProject:=LazarusIDE.ActiveProject;
  if LazProject<>nil then
    for i:=0 to LazProject.FileCount-1 do
    begin
      LazFile:=LazProject.Files[i];
      if LazFile.IsPartOfProject
      and FilenameIsPascalUnit(LazFile.Filename)
      then
        debugln(LazFile.Filename);
    end;
end;

所有用过的项目单元

要查找项目 / 包的所有当前使用单元,您可以使用以下内容。请注意,IDE 需要解析所有单元,因此可能需要一些时间。

uses
  LazLoggerBase, LazIDEIntf, ProjectIntf;

procedure ListProjectUnits;
var
  LazProject: TLazProject;
  i: Integer;
begin
  LazProject:=LazarusIDE.ActiveProject;
  if LazProject<>nil then
  begin
    Units:=LazarusIDE.FindUnitsOfOwner(LazProject,[fuooListed,fuooUsed]); // 添加 fuooPackages 以包含包中的单位
    try
      for i:=0 to Units.Count-1 do
        debugln('Filename=',Units[i]);
    finally
      Units.Free;
    end;
  end;
end;

项目的 .lpr、.lpi 和 .lps 文件

uses
  LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
var
  LazProject: TLazProject;
begin
  LazProject:=LazarusIDE.ActiveProject;
  // 每个项目都有一个 .lpi 文件:
  DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);

  // 如果项目会话信息存储在单独的 .lps 文件中:
  if LazProject.SessionStorage<>pssNone then
    DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);

  // 如果项目有 .lpr 文件,那么它是主要的源文件:
  if (LazProject.MainFile<>nil)
  and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
    DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
end;

项目的可执行文件 / 目标文件名

有一个宏 $(TargetFile),可用于路径和外部工具。您可以在代码中查询宏:

uses MacroIntf;

function MyGetProjectTargetFile: string;
begin
  Result:='$(TargetFile)';
  if not IDEMacros.SubstituteMacros(Result) then
    raise Exception.Create('unable to retrieve target file of project');
end;

有关更多宏的信息,请参阅此处:IDE Macros in paths and filenames

添加您自己的项目类型

您可以在“新建 ...”对话框中添加项目:

  • 请参阅 IDEIntf 包的单元 ProjectIntf。
  • 请参阅项目模板

添加您自己的文件类型

您可以在“新建 ...”对话框中添加项目:

  • 请参阅 IDEIntf 包的单元 ProjectIntf。
  • 为普通单元选择基类 TFileDescPascalUnit,为新表单 / 数据模块选择 TFileDescPascalUnitWithResource。

添加新文件类型

uses ProjectIntf;
...
  { TFileDescText }

  TFileDescMyText = class(TProjectFileDescriptor)
  public
    constructor Create; override;
    function GetLocalizedName: string; override;
    function GetLocalizedDescription: string; override;
  end;
...

procedure Register;

implementation

procedure Register;
begin
  RegisterProjectFileDescriptor(TFileDescMyText.Create,FileDescGroupName);
end;

{ TFileDescMyText }

constructor TFileDescMyText.Create;
begin
  inherited Create;
  Name:='MyText'; // 不要翻译这个
  DefaultFilename:='text.txt';
  AddToProject:=false;
end;

function TFileDescText.GetLocalizedName: string;
begin
  Result:='My Text'; // 用 resourcestring 替换它
end;

function TFileDescText.GetLocalizedDescription: string;
begin
  Result:='An empty text file';
end;

添加新表单类型

uses ProjectIntf;

...
  TFileDescPascalUnitWithMyForm = class(TFileDescPascalUnitWithResource)
  public
    constructor Create; override;
    function GetInterfaceUsesSection: string; override;
    function GetLocalizedName: string; override;
    function GetLocalizedDescription: string; override;
  end;
...

procedure Register;

implementation

procedure Register;
begin
  RegisterProjectFileDescriptor(TFileDescPascalUnitWithMyForm.Create,FileDescGroupName);
end;

{ TFileDescPascalUnitWithMyForm }

constructor TFileDescPascalUnitWithMyForm.Create;
begin
  inherited Create;
  Name:='MyForm'; // 不要翻译这个
  ResourceClass:=TMyForm;
  UseCreateFormStatements:=true;
end;

function TFileDescPascalUnitWithMyForm.GetInterfaceUsesSection: string;
begin
  Result:='Classes, SysUtils, MyWidgetSet';
end;

function TFileDescPascalUnitWithForm.GetLocalizedName: string;
begin
  Result:='MyForm'; // 用 resourcestring 替换它
end;

function TFileDescPascalUnitWithForm.GetLocalizedDescription: string;
begin
  Result:='Create a new MyForm from example package NotLCLDesigner';
end;

以 lpi/lps 存储自定义值

您可以通过 CustomData 和 CustomSessionData 中的键值对存储您自己的数据。

 LazarusIDE.ActiveProject.CustomData['Name'] := Value ; // 存储在 lpi-x-platform 中,共享数据
 LazarusIDE.ActiveProject.CustomSessionData['Name'] := Value ; // 存储在 lps 中 - 只有本机

您可以使用 Packages 执行各种操作:

搜索所有包

迭代 IDE 中加载的所有包(自 0.9.29 起)。

uses PackageIntf;
...
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
  writeln(PackageEditingInterface.GetPackages(i).Name);

搜索包含名称的包

uses PackageIntf;
...
var
  Pkg: TIDEPackage;
begin
  Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
  if Pkg<>nil then
    ...
end;

注意:FindPackageWithName 不会打开包编辑器。为此使用 DoOpenPackageWithName。

获取已安装软件包的 lpk 文件名

uses PackageIntf;
...
var
  Pkg: TIDEPackage;
begin
  Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
  if Pkg<>nil then
    LPKFilename:=Pkg.Filename;
end;

安装包

注意:仅安装带有 IDE 插件的软件包。安装其他软件包可能会使 IDE 不稳定。

uses PackageIntf, contnrs;
...
  PkgList:=TObjectList.create(true);
  try
    Pkg:=TLazPackageID.Create;
    Pkg.Name:='Cody';
    PkgList.Add(Pkg);
    // 检查 IDE 是否可以找到 cody.lpk 和所有依赖项
    // 如果看起来很奇怪,IDE 会提示一些警告 / 确认。
    if not PackageEditingInterface.CheckInstallPackageList(PkgList,[]) then
      exit;
    // 在这个例子中我们已经检查过,所以跳过警告并重建 IDE
    if PackageEditingInterface.InstallPackages(PkgList,[piiifSkipChecks,piiifRebuildIDE])<>mrOK then
      exit;
  finally
    PkgList.Free;
  end;

打开包文件(lpk)

uses PackageIntf, FileUtil;
...
var
  pkg: TIDEPackage;
begin
  if PackageEditingInterface.DoOpenPackageFile(LPKFilename,[pofAddToRecent],false)<>mrOk then
    exit;
  Pkg:=PackageEditingInterface.FindPackageWithName(ExtractFilenameOnly(LPKFilename));
  ...
end;
Warning-icon.png

Warning: IDE 会在空闲时自动关闭未使用的软件包。永远不要引用 TIDEPackage。

找到一个单元的包裹

你有一个单元的文件名,例如 '/home/user/unit.pas',你想知道它属于哪个包和项目使用这个:

uses Classes, PackageIntf, ProjectIntf;
...
var
  Owners: TFPList;
  i: Integer;
  o: TObject;
begin
  Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',[]);
  if Owners=nil then begin
    // 单元与项目 / 包没有直接关联
    // 也许单元出于某种原因没有添加,但可以访问
    // 搜索所有项目 / 包的所有单元路径
    // 注意:这会导致错误命中
    Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',
      [piosfExcludeOwned,piosfIncludeSourceDirectories]);
  end;
  if Owners=nil then exit;
  try
    for i:=0 to Owners.Count-1 do begin
      o:=TObject(Owners[i]);
      if o is TIDEPackage then begin
        writeln('Owner is package ',TIDEPackage(o).Name);
      end else if o is TLazProject then begin
        writeln('Owner is project ',TLazProject(o).ProjectInfoFile);
      end;
    end;
  finally
    Owners.Free;
  end;

查找项目 / 包所需的包

您有一个项目或包,并且您想知道需要哪个包,使用:

uses Classes, PackageIntf, ProjectIntf;
...
var
  lPkgList: TFPList;
  i: Integer;
begin
  PackageEditingInterface.GetRequiredPackages(LazarusIDE.ActiveProject, lPkgList, [pirNotRecursive]);
  try
    for i := 0 to lPkgList.Count - 1 do begin
      writeln('required package is ' + TIDEPackage(lPkgList[i]).Filename);
    end;
  finally
    lPkgList.Free;
  end;
...

窗口(Windows)

基本上有四种类型的 IDE 窗口。

  • 主 IDE 栏是 Application.MainForm。它始终存在。
  • 浮动 / 可停靠窗口,如源代码编辑器,对象检查器和消息。
  • 模态表单,如查找对话框,选项对话框和问题。
  • 提示和完成表格

添加新的可停靠 IDE 窗口

什么是可停靠的 IDE 窗口:像源代码编辑器或对象检查器这样的 Windows 是浮动窗口,如果安装了扩展包,则可以停靠它,并在下次 IDE 启动时存储和恢复其状态,位置和大小。为了恢复窗口,IDE 需要在 IDEIntf 包的单元 IDEWindowIntf 中定义的创建者。每个可停靠窗口必须具有唯一名称。不要使用像 'FileBrowser' 这样的通用名称,因为这很容易与其他包冲突。并且不要使用像“XYZ”这样的短名称,因为创建者负责以此名称开头的所有表单。

如何注册可停靠的 IDE 窗口

请记住选择一个有效的 pascal 标识符的长唯一名称。您的窗口可以包含您想要的任何标题。

uses SysUtils, IDEWindowIntf;
...
var MyIDEWindow: TMyIDEWindow = nil;

procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
begin
  // 检查名称
  if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
  // 如果尚未创建表单并禁用自动调整大小
  IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
  ... init the window ...
  AForm:=MyIDEWindow;
end;

procedure Register;
begin
  IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
  // 表单的默认边界是:
  // Left=100, Top=Screen.Height div 2, Width=300, Height=Screen.Height div 5
  // 当 IDE 需要此窗口的实例时,它会调用 CreateMyIDEWindow 过程。
end;

显示 IDE 窗口

不要使用 Show。使用:

IDEWindowCreators.ShowForm(MyIDEWindow,false);

这将适用于对接。对接系统可以将表格包裹到对接站点中。BringToFront 参数告诉对接系统使表单及其所有父站点可见,并将顶层站点放在前面。

有关 IDEWindowCreators 和 SimpleLayoutStorage 的注释

IDEWindowCreators.SimpleLayoutStorage 只存储曾经打开过的所有表单的 BoundsRect 和 WindowState。如果没有安装 dockmaster,它将用作后备。即使安装了 DockMaster,它也会存储状态,因此在卸载 dockmaster 时,将恢复表单边界。

所有可停靠表单都使用 IDEWindowCreators 来注册自己并显示表单。在显示表单时,Creator 会检查是否安装了 IDEDockMaster,并将显示委托给它。如果没有安装 IDEDockMaster,它只显示表单。IDEDockMaster 可以使用 IDEWindowCreators 中的信息按名称创建表单,并在第一次显示表单时了解放置表单的位置。有关更多详细信息,请参阅包 AnchorDockingDsgn 和 EasyDockMgDsgn。

代码工具

CodeTools 是一个包,提供了大量的功能来解析、搜索和更改 pascal 源代码以及其他语言的一些基本功能。您也可以在没有 IDE 的情况下使用它们。在 IDE 中使用它们之前,建议您首先阅读 Codetools

在调用 IDE 中的任何 CodeTools 函数之前,您应该将源代码编辑器的当前更改提交到 CodeTools 缓冲区:

uses LazIDEIntf;
...
  // 将源代码编辑器中的更改保存到 codetools
  LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1:提交所有源代码编辑器

将资源指令添加到文件中

这为 pascal 单元添加 {$R example.res}:

procedure AddResourceDirectiveToPascalSource(const Filename: string);
var
  ExpandedFilename: String;
  CodeBuf: TCodeBuffer;
begin
  // 确保修剪文件名并包含完整路径
  ExpandedFilename:=CleanAndExpandFilename(Filename);
  
  // 将源代码编辑器中的更改保存到 codetools
  LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);

  // 加载文件
  CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);

  // 添加资源指令
  if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
    LazarusIDE.DoJumpToCodeToolBossError;
end;

codetools 还提供 FindResourceDirective 和 RemoveDirective 等函数。

获取单元和包含文件的搜索路径

IDE 中有许多不同的搜索路径来自项目,包,fpc 和 lazarus 目录,并且有许多类型的路径:在解析宏之前或之后,有或没有继承的搜索路径,作为相对路径或绝对路径。目录中的所有文件共享同一组搜索路径。您可以通过询问 codetools 来完全解析每个目录的搜索路径:

uses CodeToolManager;
...

Dir:=''; // 空目录用于新文件,并且具有与项目目录相同的设置

// 获取包含文件的搜索路径:
Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);

// 获取单元的搜索路径:
// 此搜索路径将传递给编译器。
// 它包含包输出目录,但不包含包源目录。
Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);

// 只能为 IDE 提供额外的单元搜索路径(不传递给编译器)
Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);

// 完整的搜索路径还包含单元的所有包源路径:
Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);

源代码编辑

激活源代码编辑器

uses SrcEditorIntf;
...
Editor:=SourceEditorManagerIntf.ActiveEditor;
if Editor=nil then exit;
Filename:=Editor.FileName;
ScreenPos:=Editor.CursorScreenXY;
TextPos:=Editor.CursorTextXY;

SynEdit

获取 TSynEdit 的设置

当您使用 TSynEdit 进行对话时,如果要使用与源代码编辑器相同的字体和设置,请使用:

uses SrcEditorIntf;
...
SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);

获取 SynEdit 的高亮设置

如果您有一个带有高亮的 TSynEdit 的对话框,并且您希望使用与源代码编辑器高亮相同的颜色来使用此语言:

使

uses SrcEditorIntf;
...
SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);

请参阅示例:SQLStringsPropertyEditorDlg 单元中的 TSQLStringsPropertyEditorDlg.Create。

帮助

添加源的帮助

首先创建一个 THelpDatabase:

HelpDB := TFPDocHTMLHelpDatabase(
  HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
  TFPDocHTMLHelpDatabase,true));
HelpDB.DefaultBaseURL := 'http://your.help.org/';

FPDocNode := THelpNode.CreateURL(HelpDB,
  'Package1 - A new package',
  'file://index.html');
HelpDB.TOCNode := THelpNode.Create(HelpDB,FPDocNode);// 一次作为 TOC
DirectoryItem := THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
  '*.pp;*.pas',false);// 一次作为正常页面
HelpDB.RegisterItem(DirectoryItem);

在消息窗口中添加行

unit IDEMsgIntf;
...
var Dir: String;
begin
  Dir:=GetCurrentDir;
  IDEMessagesWindow.BeginBlock;
  IDEMessagesWindow.AddMsg('unit1.pas(30,4) Error: Identifier not found "a"',Dir,0);
  IDEMessagesWindow.EndBlock;
end;

IDE 选项框架

打开特定框架中的选项

uses LazIDEIntf;
...
LazarusIDE.DoOpenIDEOptions(TYourIDEOptionsFrame);

其它

添加宏

您可以添加自己的 IDE 宏:

uses MacroIntf, MacroDefIntf;

procedure Register;
begin
  // 注册具有固定值的宏:
  IDEMacros.Add(TTransferMacro.Create('MyMacro1','Value','Description',nil,[]));
end;

// 使用动态值注册宏需要一个对象的方法
procedure TForm1.Init;
begin
  IDEMacros.Add(TTransferMacro.Create('MyMacro','Value','Description',@OnResolveMyMacro,[]));
end;

function TForm1.OnResolveMyMacro(const s: string; const Data: PtrInt;
  var Abort: boolean): string;
// s 是参数。例如 $MyMacro(parameter)
// 数据是查询实例。可以是零。
// 如果有错误设置 Abort 为 true。
// 结果字符串可以包含宏。例如 Result:='$(FPCVer)/$(TargetOS)';
begin
  Result:='MyValue';
end;

调试 IDE

  • 像往常一样编译 IDE,例如通过 Tools/Build Lazarus。确保设置调试标志,如 -g 或 -gw2。最好是使用配置文件“Debug IDE”,其中包含许多用于调试的标志。
  • 打开 ide \ lazarus.lpi 项目。您无法直接编译此项目。
  • 根据需要设置断点等运行项目。

另请参见