Sign in Join
Wiki copytemporary

Revision 2 of 4, by WikiArchive on 11-4-2026. The page as it is now.

Extending the IDE/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 依赖项

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

菜单项

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

添加菜单项

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

pascal
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 过程:

pascal
procedure StartMyTool;
    begin
      ... 点击��项��执行的代� ...
    end;
    
    procedure Register;
    begin
      RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', ' 我的工具(&T)', nil, @StartMyTool);
    end;

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

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

pascal
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 作为快捷键的命令:

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;

配置文件

加载和保存配置

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

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

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

pascal
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;

注意:

组件

扩展现有组件

来源:1(http://lazarus.freepascal.org/index.php/topic,17733.msg98425.html#msg98425) 定制现有的 LCL 组件很简单,例如,如果要添加属性。

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

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

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

这是一个简单的例子:

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.

编写组件

主要文章: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 目录中找到:

pascal
~/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 的初始化部分中使用:

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

安装包。

隐藏组件面板中的组件

Package IDEIntf, unit componentreg:

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

编写组件编辑器

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

编写属性编辑器

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

在 Object Inspector 中隐藏属性

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

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

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

pascal
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 还不能伪造它们。这就是你必须注册新类的原因。这意味着:

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;
    ...

界面设计器(Designer)

编写一个设计器中间层

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

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

pascal
uses FormEditingIntf;
    
    ...
    
    NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);
    
    // 或者如果您需要在创建组件之前创建名称:
    
    NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
    // ComponentClassName 将用作前缀,没有前导 T.
    // OwnerComponent 是新组件的新所有者。

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

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

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

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;

保存单元,设计器窗口

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

将设计器窗口保存为 Pascal

事件处理程序

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

设计器活动

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

例子:

pascal
GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);
pascal
GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);
pascal
GlobalDesignHook.RemoveAllHandlersForObject(Self);

GlobalDesignHook 的处理程序:

设计器活动的例子

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

pascal
...
    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 对其进行精确控制:

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;

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

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

pascal
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 中定义。

其他 IDE 事件

uses LazIDEIntf;

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

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

项目

当前的项目

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

当前项目的所有单元

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

项目检查员的所有单元

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

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 需要解析所有单元,因此可能需要一些时间。

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;

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

pascal
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),可用于路径和外部工具。您可以在代码中查询宏:

pascal
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。

添加您自己的项目类型

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

添加您自己的文件类型

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

添加新文件类型

pascal
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;

添加新表单类型

pascal
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 中的键值对存储您自己的数据。

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

包

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

搜索所有包

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

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

搜索包含名称的包

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

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

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

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

安装包

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

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;

打开包文件(lpk)

pascal
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',你想知道它属于哪个包和项目使用这个:

pascal
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;

查找项目 / 包所需的包

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

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;
    ...

窗口(Windows)

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

添加新的可停靠 IDE 窗口

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

如何注册可停靠的 IDE 窗口

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

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。使用:

pascal
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 缓冲区:

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

将资源指令添加到文件中

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

pascal
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 来完全解析每个目录的搜索路径:

pascal
uses CodeToolManager;
    ...
    
    Dir:=''; // 空目录用于新文件,并且具有与项目目录相同的设置
    
    // 获取包含文件的搜索路径:
    Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);
    
    // 获取单元的搜索路径:
    // 此搜索路径将传递给编译器。
    // 它包含包输出目录,但不包含包源目录。
    Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);
    
    // 只能为 IDE 提供额外的单元搜索路径(不传递给编译器)
    Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);
    
    // 完整的搜索路径还包含单元的所有包源路径:
    Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);

源代码编辑

激活源代码编辑器

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

SynEdit

获取 TSynEdit 的设置

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

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

获取 SynEdit 的高亮设置

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

使

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

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

帮助

添加源的帮助

首先创建一个 THelpDatabase:

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);

在消息窗口中添加行

pascal
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 选项框架

打开特定框架中的选项

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

其它

添加宏

您可以添加自己的 IDE 宏:

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;

调试 IDE

另请参见


From the Free Pascal wiki, page "Extending the IDE/zh CN", as the Internet Archive kept it on 2024-07-25. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.