Daniel
Daniel

Reputation: 1122

OnNewItem event for TMainMenu

I have a TMainMenu on a form and I want to add an event when an TMenuItem is added to the TmainMenu.

TMainMenu.OnChange(Sender: TObject; Source: TMenuItem; Rebuild: Boolean) doesn't seem to work because there is no difference in params values when adding or removing or updateing items. And I need to react only to new items.

Any ideas?

Upvotes: 0

Views: 360

Answers (1)

Cosmin Prund
Cosmin Prund

Reputation: 25678

Problem

Looking at the code for TMenuItem.Add() it's clearly obvious that the only event that's triggered is the OnChange. Because of that there's no easy and clean solution.

Clean solution - refactor your code

My first thought: surely you know when a menu item is added to the menu, it's your code that's adding it. The best option would be to simply re-factor the code so it doesn't directly add the menu item but calls a method of your choice. For example, if you're using code like this:

someMenu.Add(M); // where someMenu is an existing menu item and "M" is the new item

you could refactor it to something like this:

// procedure definition in private section of main form, or somewhere else relevant:
procedure AddSubMenu(const Where, What: TMenuItem);

// Refactor your code to do this:
AddSubMenu(someMenu, M);

// Then in the "AddSubMenu" you can do whatever you want to do for new items:
procedure TFormName.AddSubmenu(const Where, What: TMenuItem);
begin
  // Do your thing.
  // Then do the actual adding of the menu item
  Where.Add(What);
end;

Alternative solution - track existing menu items

Use the OnChange item that you know gets called, recursively walk the list of existing TMenuItems and do something with them so you know you've seen them before. For example set the Tag to 1 - you'll know items with Tag = 0 are new. Or add all items into a Dictionary so you can easily test what items are new and what items are pre-existing.

Here's an example OnChange handler using the Tag property to track rather a menu item is New or Old. Make sure you handle the initial creating of the Menu properly; For example, I'd assign the OnChange at runtime, from the form's OnCreate, after the Menu has been initialized from the DFM and after setting the Tag for all the design-time menu items to 1:

procedure TForm1.MainMenu1Change(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
var i: Integer;

  procedure VisitMenuItem(const M: TMenuItem);
  begin
    if M.Tag = 0 then
    begin
      // First time I see this TMenuItem!

      // DO my thing
      ShowMessage(M.Caption);

      // Mark the item so I don't "Do my thing" again
      M.Tag := 1;
    end;
  end;

  procedure RecursivelyVisitAllMenuItems(const Root: TMenuItem);
  var i:Integer;
  begin
    VisitMenuItem(Root);
    for i:=0 to Root.Count-1 do
      RecursivelyVisitAllMenuItems(Root.Items[i]);
  end;

begin
  for i:=0 to MainMenu1.Items.Count-1 do
    RecursivelyVisitAllMenuItems(MainMenu1.Items[i]);
end;

Upvotes: 4

Related Questions