How to remove menu item from OnsScreenMenu

Hi!

In this procedure a menu item is added but how can I remove it after selecting it?
(I want interactive menu items on run time that depend on choices made).
I have tried " OnScreenCrewmemberInfoMenu.RemoveControl(MenuItem);" but the
“Where can I find” menu Item is still there and every time I call this procedure a new “Where can I find” item is added again (so it becomes a list).
I also tried MenuItem.Destroy but it takes the menu item away before selecting.

class procedure TEventHandler.CrewmemberInformationClick(Sender: TObject);
var
I: Integer;
HS, HH: String;
MenuItem: TCastleOnScreenMenuItem;
begin
SearchString := (Sender as TCastleOnScreenMenuItem).Caption;

For I := 1 to NPCAmount do // check all NPC for their current location
begin
if NPCinfo[I].Personalia.FirstName = SearchString then
begin
Window.Controls.Remove(OnScreenCrewmemberInfoMenu);
Window.Controls.InsertFront(TalkTextLabel);
TalkTextLabel.Text.Clear;
if NPCinfo[I].Personalia.Gender = ‘m’ then HS := ‘He’;
if NPCinfo[I].Personalia.Gender = ‘m’ then HH := ‘him’;
if NPCinfo[I].Personalia.Gender = ‘f’ then HS := ‘She’;
if NPCinfo[I].Personalia.Gender = ‘f’ then HH := ‘her’;

  MenuItem := TCastleOnScreenMenuItem.Create(Application);
  MenuItem.Caption := 'Where can I find ' + HH + '?';
  MenuItem.OnClick := @TEventHandler(nil).CrewmemberLocationClick;
  OnScreenCrewmemberInfoMenu.Add(MenuItem);
  Window.Controls.Remove(OnScreenConversationMenu);
  Window.Controls.InsertFront(OnScreenCrewmemberInfoMenu);
  if NPCinfo[I].Location = 'Unknown' then LabelText := 'I really do not know.' else LabelText := HS + ' is at ' + NPCinfo[I].Location;
  OnScreenCrewmemberInfoMenu.RemoveControl(MenuItem);
end;

end;
Window.Controls.Remove(OnScreenCrewmemberMenu);
end;

how can I remove it after selecting it?

Try OnScreenCrewmemberInfoMenu.MenuItems.RemoveControl(MenuItem);. Note that it’s a simple TCastleVerticalGroup under the hood.

Also note that in the code above you Add(MenuItem); and then immediately RemoveControl(MenuItem); - i.e. it won’t ever appear on screen. I think the code after Add(MenuItem); was supposed to go inside CrewmemberLocationClick?

1 Like

Thanks!
You were right it did not appear so I just put OnScreenCrewmemberInfoMenu.MenuItems.RemoveControl(MenuItem) in the CrewmemberLocationClick procedure.
For that to work I had to redeclare MenuItem as a global variable.

Thanks for your help, it saved me many hours of trying things out.

1 Like

Hi!

I have an additional question about this:

How can I remove a menu item from the itemlist directly after selecting it?
For instance: When I click on ‘Hi!’ it goes on class procedure StartTalkNPCClick.
First line in procedure StartTalkNPCClick is:
if ConversationMenuItem.Caption = ‘Hi!’ then Conversationmenu.menuItems.RemoveControl(ConversationMenuItem);
But the ‘Hi’ item remains in the list so this does not work.

I want an interactive menu so every menu item should display once, perform an action after clicking on it and then it should be removed from the list.

ConversationMenu := TCastleOnScreenMenu.Create(Application);
ConversationMenuItem := TCastleOnScreenMenuItem.Create(Application);
ConversationMenuItem.Caption := ‘Hi!’;
ConversationMenuItem.OnClick := @TEventHandler(nil).StartTalkNPCClick;
ConversationMenu.Add(ConversationMenuItem);
ConversationMenuItem := TCastleOnScreenMenuItem.Create(Application);
ConversationMenuItem.Caption := ‘Bye’;
ConversationMenuItem.OnClick := @TEventHandler(nil).QuitTalkNPCClick;
ConversationMenu.Add(ConversationMenuItem);

Removing the menu item by Conversationmenu.menuItems.RemoveControl(ConversationMenuItem); should work. But the validity of your code depends on what is the value of ConversationMenuItem variable inside

if ConversationMenuItem.Caption = ‘Hi!’ then Conversationmenu.menuItems.RemoveControl(ConversationMenuItem);

As you maybe using a global variable ConversationMenuItem? That you override in your initialization below? If yes, this is a bug – the global ConversationMenuItem is effectively set to 'last menu item", so the check doesn’t do what you probably expect.

The OnClick event has a Sender parameter that you should use, and in this case there’s no even need to check it’s caption. Just something like

procedure xxx.StartTalkNPCClick(Sender: TObject);
begin
  Conversationmenu.MenuItems.RemoveControl(Sender as TCastleOnScreenMenuItem);
end;

should work.

Er… yes, I use a global variable ConversationMenuItem.
:slight_smile:
That is because I use several menus and I want every menu to be able to access menu items from other menus that are defined in other procedures. So the more global question is probably how I can acces the methods/values/contents of local variables from other procedures / click events?

Your suggested “Conversationmenu.MenuItems.RemoveControl(Sender as TCastleOnScreenMenuItem);” does the job removing the “Hi!” menu item. Great!
But if I want to start a new conversation again with StartTalkNPC it should be added again and on top of the menu list again as first item. How can I achieve this?

You need to store your TCastleOnScreenMenuItem instances then somehow. How exactly – depends on your data layout. Definitely a single global variable of type TCastleOnScreenMenuItem is not enough, since you have multiple TCastleOnScreenMenuItem instances.

If you don’t have already any dedicated data structure to store dialog options, I suggest you create one, like TPossibleAnswers. You can store a list of objects e.g. using TObjectList which we describe (the generic version) in Modern Object Pascal Introduction for Programmers .

Thanks, I’ll try that. But is it possible with OnScreenMenu to:

  1. Sort menu items in the OnScreenMenu so that for instance the “Hi!” menu item is always placed as first one / top menu item and the “Bye” as last item, even if I add or remove other menuitems in this list during interaction?
  2. Keep the top position (Y coordinate) of the OnScreenMenu the same when menu items are added? (I now notice the OnScreenMenu is moving up the screen when I add menu items).

AD 1 - you can insert new control at a specified position, using InsertControl(const Index: Integer; const NewItem: TCastleUserInterface) instead of InsertFront / InsertBack.

There is no API call to reorder existing UI control now, though.

AD 2 - The TCastleOnScreenMenu.MenuItems is a TCastleVerticalGroup that automatically adjusts to the heights of children. The fact that is shrinks / expands when removing / adding items is a feature. To avoid it, you can e.g. add a TCastleUserInterface with some Height to replace the TCastleOnScreenMenuItem, thus reserving the same position.

Maybe you should just make it simpler. If you want to keep the same height of everything always, then do not remove the menu items at all. Instead make the buttons (TCastleOnScreenMenuItem is a descendant of TCastleButton) disabled. This will be easier for you.

So when someone clicks an item, instead of removing the Sender, just do

(Sender as TCastleOnScreenMenuItem).Enabled := false

You could also set Caption to empty, remember to later restore it.

Note that it means that if someone chooses an item in the middle, then the “hole” will appear. E.g. if you have 3 items, and you make the 2nd item disabled (as above), then the 1st and 3rd menu items will be now separated by a hole (disabled menu item). How to deal with it depends on what you want to show, various games do it differently (moving the dialogues options up, or using totally different UI for them – like comic bubbles in our “The Unholy Society”, or a “dialog wheel” like in Mass Effect and others).

Thanks for your suggestions, I’m gonna try things out now, keep you posted.
:wink:
What is the full replacement code line for your suggested (Sender as TCastleOnScreenMenuItem).Enabled := false
instead of Conversationmenu.MenuItems.RemoveControl(Sender as TCastleOnScreenMenuItem); ?

What is the full replacement code line for your suggested (Sender as TCastleOnScreenMenuItem).Enabled := false
instead of Conversationmenu.MenuItems.RemoveControl(Sender as TCastleOnScreenMenuItem); ?

You have to decide how to show a menu item that is no longer available, but still reserves space on the screen. TCastleOnScreenMenuItem is a descendant of TCasleButton so you have lots of ways to style it (see e.g. engine demo in examples/component_gallery/ that shows how to use buttons).

My initial suggestion is just to set Caption to empty, so do

(Sender as TCastleOnScreenMenuItem).Enabled := false;
(Sender as TCastleOnScreenMenuItem).Caption := '';

But this is really something you have to decide, to make it look reasonable / pretty in your game :slight_smile:

Yes, but I am looking for the right syntax, because with “ConversationMenuItem.Enabled := false;”
I get an error:
" identifier idents no member “enabled”

Though ConversationMenuItem.Caption := ‘’; works okay.
??

Yes, but I am looking for the right syntax, because with “ConversationMenuItem.Enabled := false;”
I get an error:
" identifier idents no member “enabled”

Ups, sorry – I gave you wrong information. I said that TCastleOnScreenMenuItem descends from TCastleButton, and that is has an Enabled property. That was wrong, looking at code now. I confused it with an old TCastleMenuButton, that indeed descends from TCastleButton. But I deprecated TCastleMenuButton some time ago. And the new TCastleOnScreenMenuItem descends from TCastleUserInterface (more generic class), as that was less convoluted.

Well, no biggie. I just added TCastleOnScreenMenuItem.Enabled property :slight_smile:

Get the latest engine version from GitHub ( GitHub - castle-engine/castle-engine: Cross-platform (desktop, mobile, console) 3D and 2D game engine supporting many asset formats (glTF, X3D, Spine...) and using modern Object Pascal ) or the binary version from our main page: https://castle-engine.io/ . Give Jenkins a few hours since this post to generate it.

And then Enabled will be available.

Thanks for adding this.
Now the caption text is still visible but the “button” cannot be clicked, I guess that’s the purpose.
Maybe I’m asking too much now :wink: but is it possible to “ghost” the caption text also when it is disabled?

The disabled caption should have a different color, set on menu by NonFocusableItemColor property ( Castle Game Engine: CastleOnScreenMenu: Class TCastleOnScreenMenu ).