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

From Lazarus wiki
Jump to navigationJump to search
Line 23: Line 23:
 
=菜单项=
 
=菜单项=
  
你可以添加新的菜单项,隐藏或移动已经存在的菜单项,也可以添加子菜单和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.
+
您可以添加新菜单项,隐藏或移动现有菜单项并添加子菜单、菜单区(菜单区是整个菜单中的一块区域,它可以是一个包含子菜单的菜单项,也可以是由分隔符分隔出来的一块区域。如果菜单区中没有子项,则该菜单区将不可见。比如 itmFileNew 就是一个用分隔符分隔的菜单区,它里面包含了“新建单元”、“新建窗体”、“新建...” 等菜单项,itmFileRecentOpen 也是一个菜单区,它只是一个菜单项,包含“最近打开的文件”子菜单)。对于您自己的窗体,您可以注册 TMainMenu/TPopupMenu,以便其它包可以进一步扩展您的窗体。IDEIntf 包的 MenuIntf 单元包含菜单的所有注册函数和 IDE 本身的许多标准菜单项。
  
== 添加一个菜单项 ==
+
== 添加菜单项 ==
  
诸如'View Object Inspector'之类的单独一个菜单项被称作一个'''TIDEMenuCommand'''。你可以使用'''RegisterIDEMenuCommand'''来创建一个这种菜单项,这个函数有两种形式(参数挺多的):
+
像“查看->对象查看器”这样的单个菜单项称为 TIDEMenuCommand。您可以使用 RegisterIDEMenuCommand 创建一个,它有两个带有大量参数的格式:
  
 
<syntaxhighlight>
 
<syntaxhighlight>
function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
+
function RegisterIDEMenuCommand(
                                const Name, Caption: string;
+
    Parent: TIDEMenuSection;
                                const OnClickMethod: TNotifyEvent = nil;
+
    const Name, Caption: string;
                                const OnClickProc: TNotifyProcedure = nil;
+
    const OnClickMethod: TNotifyEvent = nil;
                                const Command: TIDECommand = nil;
+
    const OnClickProc: TNotifyProcedure = nil;
                                const ResourceName: String = ''
+
    const Command: TIDECommand = nil;
                                ): TIDEMenuCommand; overload;
+
    const ResourceName: String = ''
function RegisterIDEMenuCommand(const Path, Name, Caption: string;
+
    ): TIDEMenuCommand; overload;
                                const OnClickMethod: TNotifyEvent = nil;
+
 
                                const OnClickProc: TNotifyProcedure = nil;
+
function RegisterIDEMenuCommand(
                                const Command: TIDECommand = nil;
+
    const Path, Name, Caption: string;
                                const ResourceName: String = ''
+
    const OnClickMethod: TNotifyEvent = nil;
                                ): TIDEMenuCommand; overload;
+
    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>
 
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>
 
<syntaxhighlight>
uses ... IDECommands, MenuIntf;
+
uses
 +
  ... Classes, LCLType, Dialogs, IDECommands, MenuIntf;
 +
 
 
...
 
...
 +
 +
procedure StartMyTool(Sender :TObject);
 +
begin
 +
  ShowMessage('Hello World!');
 +
end;
 +
 +
procedure Register;
 
var
 
var
 
   Key: TIDEShortCut;
 
   Key: TIDEShortCut;
Line 71: Line 85:
 
   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;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>

Revision as of 13:25, 12 August 2019

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 邮件列表。

菜单项

您可以添加新菜单项,隐藏或移动现有菜单项并添加子菜单、菜单区(菜单区是整个菜单中的一块区域,它可以是一个包含子菜单的菜单项,也可以是由分隔符分隔出来的一块区域。如果菜单区中没有子项,则该菜单区将不可见。比如 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;

配置文件

加载和保存配置

所有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;

组件

编写组件

你可以通过包编辑器(package editor)创建新组件。例如:要创建或打开一个包,点击add,再点New Component,填写条目:Ancestor Type = TButton,New class name = TMyButton,palette page = Misc,Unit file name = mybutton.pas (这个文件不会被创建的),单元名称设置为MyButton然后点击ok。 (想起小时候看的一本win95还是win98的书,那里面双击叫双咔嗒,单击叫单咔嗒。题外话,可乐加怀旧。)

给这个新组件添加个图标(显示在控件面板上)

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:

 ~/lazarus/tools/lazres mybutton.lrs tmybutton.png

This creates an pascal include file, which is used in the initialization section of mybutton.pas:

 initialization
   {$I mybutton.lrs}

Install the package.

在控件面板上隐藏一个组件

Package IDEIntf, unit componentreg:

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

编写组件编辑器(component editors)

组件编辑器应该用来处理双击界面编辑器中的一个控件,或者处理添加右键菜单项(在右击一个组件的时候)。编写一个组件编辑器是很简单的。 看看包IDEIntf中的单元componenteditors.pas,里面很多例子。例如双击组件时唤出代码编辑器,CheckListBox的实现是TCheckListBoxComponentEditor。

编写属性编辑器

在对象观察器(Object Inspector)中的每种类型(integer, string, TFont, ...)的属性都需要一个属性编辑器。如果你的组件包含一个名为Password的字符串类型的属性,你得相应地定义一个属性编辑器。 包IDEIntf中的单元propedits.pp包含很多标准的正被IDE使用的属性编辑器。例如,TStringPropertyEditor是所有字符串值的默认编辑器,但TComponentNamePropertyEditor就会更有针对性,它只处理TComponent.Name。

设计器(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 fpGUI. 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.
  • Open the the example project 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.

如何在设计观察器和对象观察器中选择一个组件

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

事件处理

IDE中有一些事件是可以被你编写的插件处理的。

界面设计器事件

在propedits.pp中有个"GlobalDesignHook"对象,它维护了几个和界面设计相关的事件。每个事件(event)会调用一个队列的事件处理(handler)。默认的事件处理是IDE添加的。你可以用AddHandlerXXX和RemoveHandlerXXX方法来添加你自己的事件处理。它们会在默认的事件处理被调用之前而被调用。

例子一枚:

 添加你的handler(通常在构造函数干这事儿):
   GlobalDesignHook.AddHandlerComponentAdded(@YourOnComponentAdded);
删除你的handler: GlobalDesignHook.RemoveHandlerComponentAdded(@YourOnComponentAdded);
你可以一下干掉所有的handlers。例如,把这行加到你的析构函数里应该是个好点子: GlobalDesignHook.RemoveAllHandlersForObject(Self);

GlobalDesignHook的handler们:

 // lookup root
 ChangeLookupRoot
   Called when the "LookupRoot" changed.
   The "LookupRoot" is the owner object of the currently selected components.
   Normally this is a TForm.
// methods CreateMethod GetMethodName GetMethods MethodExists RenameMethod ShowMethod Called MethodFromAncestor ChainCall
// 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.
// persistent objects GetObject GetObjectName GetObjectNames
// modifing Modified Revert RefreshPropertyValues // Selection SetSelection GetSelection

如何在一个设计窗体被修改后得到通知

每一个设计好的LCL窗体有一个TIDesigner类型的设计器。IDE创建的设计器都是TComponentEditorDesigner类型的,该类型定义在包IDEIntf中的单元componenteditors。 例如:

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;

Project events

These events are defined in unit LazIDEIntf.

  • LazarusIDE.AddHandlerOnProjectClose: called before a project is closed
  • LazarusIDE.AddHandlerOnProjectOpened: called after the project was completely opened (for example all required packages were loaded, units were opened in the source editor)
  • LazarusIDE.AddHandlerOnSavingAll: called before IDE saves everything
  • LazarusIDE.AddHandlerOnSavedAll: called after IDE saved everything
  • LazarusIDE.AddHandlerOnProjectBuilding: called before IDE builds the project
  • LazarusIDE.AddHandlerOnProjectDependenciesCompiling: called before IDE compiles package dependencies of project
  • LazarusIDE.AddHandlerOnProjectDependenciesCompiled: called after IDE compiled package dependencies of project

Other IDE events

  • LazarusIDE.AddHandlerOnIDERestoreWindows: called when IDE is restores its windows (before opening the first project)
  • LazarusIDE.AddHandlerOnIDEClose: called when IDE is shutting down (after closequery, so no more interactivity)
  • LazarusIDE.AddHandlerOnQuickSyntaxCheck: called when the menu item or the shortcut for Quick syntax check is executed

Project

Current Project

The current main project can be obtained by LazarusIDE.ActiveProject. (unit 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:

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;

The .lpr, .lpi and .lps file of a project

uses LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
var
  LazProject: TLazProject;
begin
  LazProject:=LazarusIDE.ActiveProject;
  // every project has a .lpi file:
  DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);

  // if the project session information is stored in a separate .lps file:
  if LazProject.SessionStorage<>pssNone then
    DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);

  // If the project has a .lpr file it is the main source file:
  if (LazProject.MainFile<>nil)
  and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
    DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
end;

The executable / target file name of a project

There is a macro $(TargetFile), which can be used in paths and external tools. You can query the macro in code:

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;

See here for more macros: IDE Macros in paths and filenames.

Add your own project type

You can add items to the 'New ...' dialog:


Add your own file type

You can add items to the 'New ...' dialog:

  • See the unit ProjectIntf of the package IDEIntf.
  • Choose a base class TFileDescPascalUnit for normal units or TFileDescPascalUnitWithResource for a new form/datamodule.

Add a new file type

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'; // do not translate this
  DefaultFilename:='text.txt';
  AddToProject:=false;
end;

function TFileDescText.GetLocalizedName: string;
begin
  Result:='My Text'; // replace this with a resourcestring
end;

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

Add a new form type

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'; // do not translate this
  ResourceClass:=TMyForm;
  UseCreateFormStatements:=true;
end;

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

function TFileDescPascalUnitWithForm.GetLocalizedName: string;
begin
  Result:='MyForm'; // replace this with a resourcestring
end;

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

Packages

Search in all packages

Iterate all packages loaded in the IDE (since 0.9.29).

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

Search a package with a name

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

Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.

Windows

There are basically four types of IDE windows.

  • 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

What is a dockable IDE window: 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.

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.

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

procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
begin
  // check the name
  if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
  // create the form if not already done and disable autosizing
  IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
  ... init the window ...
  AForm:=MyIDEWindow;
end;

procedure Register;
begin
  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
  // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
end;

Showing an IDE window

Do not use Show. Use:

IDEWindowCreators.ShowForm(MyIDEWindow,false);

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.

Notes about IDEWindowCreators and SimpleLayoutStorage

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.

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

Before using the codetools you should commit the current changes of the source editor to the codetool buffers:

uses LazIDEIntf;
...
  // save changes in source editor to codetools
  LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors

Adding a resource directive to a file

This adds a {$R example.res} to a pascal unit:

procedure AddResourceDirectiveToPascalSource(const Filename: string);
var
  ExpandedFilename: String;
  CodeBuf: TCodeBuffer;
begin
  // make sure the filename is trimmed and contains a full path
  ExpandedFilename:=CleanAndExpandFilename(Filename);
  
  // save changes in source editor to codetools
  LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);

  // load the file
  CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);

  // add the resource directive
  if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
    LazarusIDE.DoJumpToCodeToolBossError;
end;

The codetools provides also functions like FindResourceDirective and RemoveDirective.

Help

Adding help for sources

First create a 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);// 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

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;