4

I have a menu structure like this:

1. Option A
    1.1 Option B
        1.1.1 Option C
        1.1.2 Option D
    1.2 Option C
        1.2.1 Option B
        1.2.2 Option D
    1.3 Option D
        1.3.1 Option B
        1.3.2 Option C
2. Option B
    2.1 Option A
        2.1.1 Option C
        2.1.2 Option D
    2.2 Option C
        2.2.1 Option A
        2.2.2 Option D
    2.3 Option D
        2.3.1 Option A
        2.3.2 Option C
3. Option C
    3.1 Option A
        3.1.1 Option B
        3.1.2 Option D
    3.2 Option B
        3.2.1 Option A
        3.2.2 Option D
    1.3 Option D
        3.3.1 Option A
        3.3.2 Option B
4. Option D
    4.1 Option A
        4.1.1 Option B
        4.1.2 Option C
    4.2 Option B
        4.2.1 Option A
        4.2.2 Option C
    4.3 Option C
        4.3.1 Option A
        4.3.2 Option B

Why do I do such thing? - This menu is used to select a combination of options A,B,C,D where sequence of selected options matters.
For Example: The user clicks on menu-item 2.3.1. That results in combination B-D-A.

Now, you know how I currently do it theoretically. Actually, there are much more options to combine. But only three are to be combined at the same time.
The problem is that I have to create all menu-items (three levels deep) before the menu is shown.

Is there a way to add submenu-items just when they are needed (that is when they should be shown)?

René Hoffmann
  • 2,766
  • 2
  • 20
  • 43
  • 2
    You can create all sub-menu items at once and then just hide/show ones you need when user clicks on menu item. – Dalija Prasnikar Jan 20 '15 at 21:02
  • 1
    @DalijaPrasnikar To clarify this: I want to avoid creating all items that could be used eventually but only create those who are needed (because the user pops up their parent item). – René Hoffmann Jan 21 '15 at 10:55
  • What is the actual problem you solve using this? Looks like a kind of "expert system"? How *dynamic* is this problem? Are there also trees for a number of options that is runtime-determined? – Wolf Oct 27 '16 at 09:05

2 Answers2

6

You can add a dummy item to act as a placeholder for sub menus and then use OnClick event handler of items that have the dummy items to replace them with real items.

Below is to demonstrate only, not meant to be used in production code. It duplicates the example in the question.

procedure TForm1.PopupMenu1Popup(Sender: TObject);
var
  NewItem: TMenuItem;
  i: Integer;
begin
  PopupMenu1.Items.Clear;
  for i := 0 to 3 do begin
    NewItem := TMenuItem.Create(PopupMenu1);
    NewItem.Caption := Format('%d. Option %s', [i + 1, Chr(i + 65)]);
    NewItem.OnClick := ItemClick;
    NewItem.Tag := i;
    NewItem.Add(TMenuItem.Create(NewItem));
    PopupMenu1.Items.Add(NewItem);
  end;
end;


procedure TForm1.ItemClick(Sender: TObject);
var
  Root: TMenuItem;

  function ItemLevel(Item: TMenuItem): Integer;
  begin
    Result := 0;
    while Item.Parent <> Root do begin
      Item := Item.Parent;
      Inc(Result);
    end;
  end;

  function ExistsInTree(Item: TMenuItem; Option: Integer): Boolean;
  begin
    Result := Option = Item.Tag;
    if not Result then
      while Item.Parent <> Root do begin
        Item := Item.Parent;
        Result := Option = Item.Tag;
        if Result then
          Break;
      end;
  end;

  function LevelString(Item: TMenuItem): string;
  begin
    Result := '';
    while Item.Parent <> Root do begin
      Item := Item.Parent;
      Result := IntToStr(Item.MenuIndex + 1) + '.' + Result;
    end;
  end;

var
  Item, NewItem: TMenuItem;
  i: Integer;
  path: string;
begin
  Item := Sender as TMenuItem;
  Root := PopupMenu1.Items;

  if ItemLevel(Item) < 2 then begin
    if Item.Count = 1 then begin
      for i := 0 to 3 do begin
        if ExistsInTree(Item, i) then
          Continue;

        NewItem := TMenuItem.Create(Item);
        NewItem.OnClick := ItemClick;
        NewItem.Tag := i;
        Item.Add(NewItem);
        NewItem.Caption := Format('%s%d. Option %s',
                           [LevelString(NewItem), Item.Count - 1, Chr(i + 65)]);
        if ItemLevel(NewItem) < 2 then
          NewItem.Add(TMenuItem.Create(NewItem));
      end;
      Item.Delete(0);
    end;
  end else begin
    path := Chr(Item.Tag + 65);
    while Item.Parent <> Root do begin
      Item := Item.Parent;
      path := Chr(Item.Tag + 65) + '-' + path;
    end;
    ShowMessage(path);
  end;
end;
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • 1
    Is there a way where the user does not need to click on a item? I want to create direct children of an item when hovering (mouse over) this item. – René Hoffmann Jan 21 '15 at 10:57
  • 3
    @Rene - Users don't need to click, OnClick for items that are submenus run when the mouse is hovered. Try the example in the answer, just put an empty TPopupMenu on a form with the name PopupMenu1 and assign it to PopupMenu of the form which have a ItemClick procedure.. – Sertac Akyuz Jan 21 '15 at 12:00
0

It's a bit complicated, since Delphi doesn't have events for this in PopupMenu. What I think is that maybe it could be done the following way:

First of all, check for custom popup menus, for example TMS' one. With this you can prevent the automatic close of the popup when the user clicks on it. After that you can catch that event and dinamically add the submenus you want. But I don't know it will display it immediately, or the Popup should closed and reopened.

You can try to achieve the same with the standard popup, you can find how to prevent the closing here.

Community
  • 1
  • 1
Fenistil
  • 3,734
  • 1
  • 27
  • 31