my english is bad so i try to be short.
This fail when is in the constructor:
fNTAServices.AddActionMenu('ViewToolWindowsItem', nil, CustomMenuItem);
because the mainmenu is not fully created at the moment the addon constructor is called.
I didnt see any "ide ready" notifier in the toolsapi, so i'm using a IOTAIDENotifier that call the AddActionMenu (once) when the NotifyCode is ofnEndProjectGroupOpen.
There is a better way?
PS.
the addon is a dll not a package
EDIT:
"minimal reproducible example" test code below.
I have added a CreateAnonymousThread in the contructor to check when the 'ViewToolWindowsItem' spawn, the time is around 100 to 300+ ms on my pc.
EDIT 2:
I tried to printing out all the MainMenu names while in the constructor, some are there some not, i'm not sure if the ide window is in another thread or if some menu items are created on the ide constructor then load the plugins then add the others when the ide show.
library IDE_EXTENSION_DLL_TEST;
uses
WinAPI.Windows,
System.SysUtils, System.Classes, System.Diagnostics,
VCL.Menus,
ToolsAPI;
{$R *.res}
type
TMenuTest = class(TInterfacedObject, IOTAWizard, IOTAMenuWizard)
// IOTANotifier
procedure AfterSave;
procedure BeforeSave;
procedure Destroyed;
procedure Modified;
// IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// IOTAMenuWizard
function GetMenuText: string;
strict private
fNTAServices: INTAServices;
fCustomMenuItem: TMenuItem;
public
constructor Create;
end;
// IOTANotifier
procedure TMenuTest.AfterSave;
begin
//
end;
procedure TMenuTest.BeforeSave;
begin
//
end;
procedure TMenuTest.Destroyed;
begin
//
end;
procedure TMenuTest.Modified;
begin
//
end;
// IOTAWizard
function TMenuTest.GetIDString: string;
begin
result := 'TMenuTest';
end;
function TMenuTest.GetName: string;
begin
result := 'Add menu test plugin ';
end;
function TMenuTest.GetState: TWizardState;
begin
result := [wsEnabled];
end;
procedure TMenuTest.Execute;
begin
if Assigned(fNTAServices) then
try
{ This work fine }
fNTAServices.AddActionMenu('ViewToolWindowsItem', nil, fCustomMenuItem);
except
on e: exception do
OutputDebugString(PChar('AddActionMenu (execute) ERR > ' + e.Message));
end;
end;
// IOTAMenuWizard
function TMenuTest.GetMenuText: string;
begin
{ this is added in Help > Help Wizards and call TMenuTest.Execute }
result := 'Add menu';
end;
constructor TMenuTest.Create;
begin
inherited;
fCustomMenuItem := TMenuItem.Create(nil); // free destr
fCustomMenuItem.Caption := 'CustomMenuItem';
fCustomMenuItem.Name := 'CustomMenuItemName';
if Supports(BorlandIDEServices, INTAServices, fNTAServices) then
begin
OutputDebugString('Supports ok');
try
{ This fail with "Cannot locate menu item 'ViewToolWindowsItem'" }
fNTAServices.AddActionMenu('ViewToolWindowsItem', nil, fCustomMenuItem);
except
on e: exception do
OutputDebugString(PChar('AddActionMenu (constructor) ERR > ' + e.Message));
end;
{ This may be some bad code but it work >_> }
TThread.CreateAnonymousThread(
procedure
var
vOk: boolean;
vSW: TStopWatch;
begin
vSW := TStopWatch.Create;
vSW.Start;
repeat
vOk := true;
try
fNTAServices.AddActionMenu('ViewToolWindowsItem', nil, fCustomMenuItem);
except
on e: exception do
begin
vOk := false;
OutputDebugString(PChar('AddActionMenu (constructor) ERR > ' + e.Message));
end;
end;
until vOk;
vSW.Stop;
// i get random 100/300+ ms with a 13th gen i5
OutputDebugString(PChar('vOk True > ' + vSW.Elapsed.Milliseconds.ToString));
end).Start;
end
else
OutputDebugString('Supports fail');
end;
// INITWIZARD0001
function InitWizard(const aBorlandIDEServices: IBorlandIDEServices; aWizardRegisterProc: TWizardRegisterProc; var aWizardTerminateProc: TWizardTerminateProc): boolean; stdcall;
const
cBoolStr: array [boolean] of string = ('False', 'True');
begin
if Assigned(aBorlandIDEServices) then
begin
aWizardRegisterProc(TMenuTest.Create);
result := true;
end
else
result := false;
OutputDebugString(PChar(Format('InitWizard %s', [cBoolStr[result]])));
end;
exports
InitWizard name WizardEntryPoint;
begin
OutputDebugString('dll attach');
end.
This is the way GExperts handles this:
constructor TGExperts.Create;
begin
inherited Create;
FStartingUp := True;
InitializeGExperts;
// No idea where these 1200 ms came from, but
// basically it means that the method will be
// called when the application handles events
// the first time.
TTimedCallback.Create(DoAfterIDEInitialized, 1200, True);
end;
TTimedCallback.Create creates a TTimer and adds code to its OnTimer event that calls DoAfterIDEInitialized and after that frees the timer.
I don't know whether this is the best solution, I just inherited that code from previous developers (that comment about 1200 ms is mine). All I can say is that it works.