Extending the IDE/zh CN

From Free Pascal wiki

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

Contents

扩展 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 项目。您无法直接编译此项目。
  • 根据需要设置断点等运行项目。

另请参见