Everything About Desktop Menus
From Xojo Documentation
The Menu Bar is one of the main user interface elements in desktop apps (macOS, Windows and Linux). The user interface is in charge of displaying all the available options (Commands) normally grouped by subject or application area so the user can see and choose all the available actions that can be applied on the active item, Window or in the context of the App.
In this tutorial, we will see how to create static menus with the help of the Menu Editor included in the Xojo IDE; how to react to the menu selected by the user; how to create the menus dynamically at runtime; and how we can clone and display complete menu hierarchies on any position within a Window or Control derived from the RectControl class. Read each section and watch the accompanying videos (in Spanish with English subtitles).
In order to follow this tutorial, you should feel comfortable with the basics of Object Oriented Programming (OOP) and Event Driven Programming like the concepts of Classes, Subclasses or Delegates. If this is not the case, you can read about them in this Overview of OOP and/or watch these videos: Object-Oriented Programming Concepts, Intro to OOP 101 and Intro to OOP 201, Advanced OOP Concepts.
Contents
- 1 Menus for Desktop and the Operating Systems
- 2 The Menu Editor
- 3 Menu Items Properties
- 4 Setting special keyboard shortcuts
- 5 Setting Icons to Menu Items
- 6 Localizing the Menu Items
- 7 How to set the Action for Static Menus
- 8 Action Response Hierarchy for Menus
- 9 Creating Menu Items from Code
- 10 How to Create Submenus from Code
- 11 How to Set the Action to Menus Created from Code
- 12 How to Hide and Destroy Menu Items: Visible, Remove and Close
- 13 Display Menus…Anywhere!
- 14 Enabling and Disabling Menu Items
Menus for Desktop and the Operating Systems
Every time we create a new Desktop Project, Xojo adds by default a new instance of a menu bar that includes all the essential items. We do this so if you decide to compile and run the app it will include the minimal required structure so you can deploy it. In fact, this is the menu bar that is assigned by default to the global object App and also to the Window added by default to the project: Window1. This menu bar has the particularity that is available as an implicit instance and globally for all the items of the App. This means that it is possible to refer to the default menu bar MainMenuBar and their items directly from any method or Event of the App.
Of course, it is possible to change the menu bar name in the Inspector Panel if you want. Once the name is changed, it is automatically reflected in all the objects referencing the menu bar by default, as is the case of the App and Window1 objects.
For macOS apps you don't need to manually assign the default or any other menu bar to each Window added to the project, these will use the default menu bar. For Windows and Linux apps, you have to explicitly assign the menu bar used by every Window from the Menus section of the Inspector Panel. Of course, you can add as many menu bars as you need to the project from the Insert > Menu Bar menu or using the Insert button available in the toolbar of the project window. Once you add new menu bars to the project, you can assign any one of them from the Menus section of the Inspector Panel or from code.
The Menu Editor
It is possible to create all the menu bar hierarchy from code, but it is usually easier to edit the project menu bars from the Menu Editor. In order to access the editor, select the menu bar you want to edit from the Project Browser. These are the main items available in the Menu Editor toolbar:
- Click on these icons to see the approximate appearance of the menu bar on any of the supported operating systems. Note that this is just an interpretation and not the real appearance they will show once you deploy the app.
- Click on this icon in order to add a new first level menu item to the menu bar. You can change any first level menu item position by dragging it to another place. The moved menu item will bring with it all the menu items under its hierarchy.
- Click on this icon to add a new menu item on the selected first level menu item. You can modify its position by dragging it to the new place. You can even drop it under other first level menu hierarchy.
- Click on this icon to add a new separator item on the selected first level menu hierarchy. As with the previous options, it is also possible to change its position. Optionally, you can convert any menu item into a separator selecting it and changing its Text property to a dash line ("-").
- Click on this button to add a new submenu in the selected main menu. Optionally, you can convert any menu item into a submenu switching the Submenu option to the On position under the Behavior section of the Inspector Panel. In the same way, you can convert a submenu item into a regular menu item switching this option to the Off position. Note that if you already had added menu items to the submenu, they will be lost once you change it to a regular menu item.
- Click on this icon to convert the selected menu item to a first level menu. If the selected item is a submenu, then the available menu items under his hierarchy will be moved too. It is not possible to convert a first level menu with all the items under it to a submenu.
- Under Windows and Linux it is possible to assign accelerator keys to the menu items in order to execute the selected item. Note that Xojo will detect the accelerator keys from left to right, so if you use the same accelerator key by mistake in more than one menu item, it will be detected only for the leftmost menu. You can assign the accelerator key to a menu item, along other menu item properties, via code or using the associated Inspector.
When the Menu Editor previews the Menu Bar for the macOS operating system, you will see that it adds the Apple menu and the My Application menu; however you can't add new menu items directly to this menu. Instead, you will have to add the menu item to the first level menu where you want it to show up for Windows and/or Linux, using then the Inspector Panel to change its associated Super class from MenuItem to AppMenuItem. This is what you probably will want to do in order to add the About My App… and the Preferences menu item for your macOS apps.
In the case of the Preferences menu item for your app, you can assign the PrefsMenuItem subclass so it looks active by default and with the proper keyboard shortcut already assigned on macOS.
If you want to check all the available MenuItem subclasses you can choose from, click on the pen icon associated with the Super field on the Inspector. This picture shows the resulting window:
Menu Items Properties
When designing the menu bar from the Menu Editor, you can set several of the available MenuItem properties using the Inspector associated with every item. In fact, you'll probably find it easier to set some of the most relevant properties using the Inspector, as is the case of setting the use of the alternate key for the keyboard shortcut, the Control key or the modifier key. In addition, it is only possible to assign an accelerator key to the menu item for Windows and/or Linux from the Menu Editor using the Inspector, and not from code. Specifically, these are the available read only properties when accessed from code:.
- AlternateMenuModifier. Boolean value for the use of the Shift key under all the supported operating systems.
- MacControlKey. Boolean value for the use of the Control (Ctrl) key under macOS.
- MacOptionKey. Boolean value for the use of the Option key (Alt) under macOS.
- PCAltKey. Boolean value for the use of the Alt key on Windows and Linux keyboards.
- MenuModifier. Boolean value for the use of the Command (Cmd) key under macOS, and Control (Ctrl) on Windows and Linux.
While these are read-only properties when accesed from code, it is still possible to assign the special keys for all the possible keyboard variations when setting the keyboard shortcut for a menu item. These are the values you can use followed with a dash line ("-") and the key for the shortcut. You can combine then in any order:
- Cmd. Command key on macOS.
- Alt. Option key on macOS and Alt on Windows and Linux.
- Shift. Shift key on Windows, macOS and Linux.
- Ctrl. Control key for macOS, Windows and Linux.
For example:
Will display the same as if it would be set as:
The following are other interesting properties available under the Inspector (also of interest when creating menu items from code), although you will probably want to use their default values:
- Visible. Available under the Appearance section of the Menu Editor for all the menu items. The menu item will be visible when set to On or using the True value from code.
- AutoEnable. Available under the Behavior section for all the menu items excepting the first level menus. This has the On value (or True value, from code) by default for all the new menu items created on the fly from code. This allows that the menu item that shows up as active under the menu always it has its handler menu implemented (or a delegate method associated to his Action event, something we will see a bit later). If we use the Off value (or the False value from code), then we will have to implement the EnableMenuItems Event Handler in the window associated with the menu bar or in the App object, and decide here if the menu item will be displayed as active or not. The app will execute the code available in this event handler every time the user clicks on the menu bar, or when using any of the assigned keyboard shortcuts. For example, we would be interested in making some menu items active only when some conditions are meet, for example, if there is an item or text selected. A typical case for this would be the Copy, Cut and Paste menu items.
Setting special keyboard shortcuts
Setting a regular keyboard shortcut for your menu items is really straightforward; but, what if you want to use some of the special keys or symbols as the "delete" key or the "right arrow" key? Xojo has considered these cases too, so you only have to set the following values to the Key property on the Inspector Panel or the KeyboardShortcut property when creating new menu items on the fly from code:
- F1-F15. Function key for that range. For example: "F1".
- Tab. Graphical representation of the Tab key.
- Enter. Graphical representation of the Enter key.
- Space. Textual representation for the Space bar key.
- Del. Graphical representation for the Delete key.
- Return. Graphical representation for the Return key.
- Bksp. Graphical representation for the Backspace key.
- Esc. Graphical representation for the Escape key.
- Clear. Graphical representation for the Clear key.
- PageUp. Graphical representation for the Page Up key.
- PageDown. Graphical representation for the Page Down key.
- Left. Graphical representation for the Left Cursor key.
- Right. Graphical representation for the Right Cursor key.
- Up. Graphical representation for the Up Cursor key.
- Down. Graphical representation for the Down Cursor key.
- Help. Graphical representation for Help (closing question mark).
- Ins. Insert.
For example, the following line will set the Command + Help keyboard shortcut to a menu item:
Setting Icons to Menu Items
In addition to the text, keyboard shortcuts and accelerator keys, it is also possible to assign an icon that helps to identify the main purpose of the function or command to every menu item of the menu bar.
You can use any of the pictures added to the project for that, and set the picture to the menu item through the Icon option of the Inspector Panel for the selected menu item, or using the Icon property from code. The only thing you need to be aware of is that Xojo will not automatically resize the assigned picture to an acceptable size for his use in combination with the menu item, so you should use a Graphical Editor to generate the pictures you intend to use in your menus with the following recommended sizes:
- 16 x 16 pixels. 1x size.
- 32 x 32 pixels. 2x size in HiDPI mode.
- 48 x 48 pixels. 3x size in HiDPI mode.
In addition, some of the current operating systems guidelines for their UI propose the use of monochromatic icons for this purpose, but you are free to use pictures of any supported color depth. It is also preferable to use PNG images with transparent background.
Localizing the Menu Items
Of course you can use localized texts for the menu items. For that, remember to set all the text constants under a Module added to the project, enabling them as Dynamic constants, and to assign the localized text values for every supported language. Then, you just have to use the #moduleName.ConstantName syntax under the Text field of the Inspector Panel for every menu item you want to localize.
For example, if our project would have a LocalizedValues module with a dynamic constant in it with the name kPreferences, then we would be able to use the localized value in the Text value of a MenuItem using the following syntax:
While if you want to achieve the same from code, then you should use this:
How to set the Action for Static Menus
If you run your app after designing the menu bars and set them to your project window, then you will see that all the menu options will be grayed or inactive, even with their AutoEnable property set to On. This is because you still need to set the action to execute for every one of the menu items in first place. To do this:
- Select the window that needs to handle the menu action or the App object from the Project Browser and add a Menu Handler to it from the Insert menu.
- As result of the previous action, you will see a new Menu Handlers section in the Project Browser, showing the Code Editor associated with the new menu handler.
- Select the menu item you want to link with the code execution for the Menu Handler from the popup menu of the Inspector Panel. Or start writing the name of the menu item in the MenuItem Name field, and use the autocomplete feature to select the desired menu item.
In addition to the window objects and the global App object, you can also add Menu Handlers to the ContainerControl instances added to the project. In this case, you have to enter the exact name of the menu item in the Inspector Panel after adding the Menu Handler.
Action Response Hierarchy for Menus
Since the windows of the project and the App object can use the same menu bars, implementing menu handlers for the same menu items in every one of these (and it is even possible that the ContainerControls used in the windows layout can also react to the selection of a menu item), which one is in charge of executing the code of the menu handler? This is the order that will follow the event after a menu item selection:
- If the frontmost window has a ContainerControl that implements a Menu Handler for the selected menu, then this will be the executed code. In order to stop the event propagation, the menu handler has to return the value `True`; otherwise, the event will be propagated to the containing window.
- If the frontmost window has a Menu Handler for the selected menu item, then this will be the executed code. If not, the App object will receive the event and if it has a Menu Handler for that menu item then it will execute the code.
- If there is not a ContainerControl or Window that can catch the event of the selected menu item, then the App will execute the menu handler for the menu item, in case it has it implemented.
As you can see, depending where we decide to implement the menu handler, or if we decide to stop or not the event propagation, we can execute different actions in response of the same menu item selection from the menu. For example, it is a good idea to add to the App object the menu handlers for menu items that should be available along all the app execution, no matter which window is the active window or the user interaction.
Creating Menu Items from Code
So far, we have seen how to add and set static menus with the help of the Menu Editor and the Menu Handlers. That is, implicit instances of the menu items with static code. However, you may need to create, display (and delete) menu items on the fly. Some practical cases for this are when you don't know, for example, the amount or type of available items to show under a menu; or the options you want to make available to the user depending a type of license. For these and other use cases, the Xojo framework provides all we need to create new menu items and even to create complete menu bars from scratch that you can assign to the app windows on the fly!
All the menu items are created by default from the MenuItem class; so the following code in the Open Event of the App object will add a new menu item named MyMenu, displaying the text "My Menu Option" and reacting to the "Cmd-U" keyboard shortcut:
newMenuItem.Name = "MyMenu"
newMenuItem.Value = "My Menu Option"
newMenuItem.KeyboardShortcut = "cmd-U"
We can also use the Class Constructor omitting the name assignation to the new instance, although you probably will want to add a name to every one of your MenuItem instances, more on this later. The following code shows how to use the MenuItem constructor:
However, creating a new menu item instance doesn't imply that it will be displayed on a menu bar as a first level menu or under the hierarchy of any of the already available menu items. You still need to add it in code. For example, if we use the default menu bar added with every new Desktop Xojo project, the following line of code will add the created menu item as a first level entry in the menu's rightmost position:
We also can set the exact position we want to display our menu item using the `AddMenuAt` method, and passing as its first parameter the 0 based index value. For example, we can use the following line of code to display our menu item as the rightmost entry in the menu bar:
If you run both examples on macOS you will notice that it doesn't display the set keyboard shortcut, which makes sense. Under Windows, the first level menu items of the menu bar will display their keyboard shortcut in case they have one assigned. In fact, they will react to the keyboard shortcut always they have the corresponding Menu Handler. Now try this code in both macOS and Windows and/or Linux:
Me.MenuBar.AddMenu(newMenuItem)
Me.MenuBar.AddMenuAt(0, newMenuItem)
When this code is executed on macOS the app will exit unexpectedly, displaying the following error message:
This is because macOS apps can't use the same menu item several times. Under Windows and Linux this is not the case and the previous code will run without problem. If you need to use the same menu item several times for macOS, Windows and/or Linux deployments, then the best solution is to use the Clone method instead.
As you can expect, the Clone method clones the item menu that invokes it and this is really powerful in order to clone complete menu hierarchies from first level menus or submenus, but not for menu bars!
For example, the following code will fix the previous problem; and will work just fine under macOS, Windows and Linux:
Me.MenuBar.AddMenu(newMenuItem)
Me.MenuBar.AddMenuAt(0, newMenuItem.clone)
Until now we have limited ourselves to add new menu items as first level entries to the menu bar but how can you add new menu items under first level entries or in order to create a submenu? For this, the MenuItem class provides two methods, 1) we can get it using its object name (and not the menu item displayed text), or 2) using the zero based index as the position that such menu item has in the hierarchy it belongs to.
However, if we want to add new menu items to other menu items created using the Menu Editor, then it is even easier! In this case we can use the menu item names from our code (remember, they are global objects). For example, if we use the default menu bar added to the project, then we know that we have a menu named FileMenu (you can check the menu item names in the Project Browser). We can add a new menu item "Open…" from code using this code:
In addition, we also can use the before mentioned methods. These will be especially useful when creating dynamic menus from code:
openMenu.name = "openMenu"
Var sourceMenu As MenuItem = Me.MenuBar.Child("FileMenu") // We get the MenuItem reference whose name is "FileMenu"
If sourceMenu <> Nil Then sourceMenu.AddMenu(openMenu)
Use the Index method for cases where you prefer to use this approximation because the menu item you want to refer to has no object name or we don't know its name in advance. For example, the menu "File" has the index zero in the default menu bar because this is the one placed in the leftmost position:
openMenu.name = "openMenu"
Var sourceMenu As MenuItem = Me.MenuBar.MenuAt(0) // We get a reference to the MenuItem whose index is 0.
If sourceMenu <> Nil Then sourceMenu.AddMenu(openMenu)
As you can deduce, we can create submenus using simply the methods Append or Insert on other menu items added (or that we will add) to first level menu item entries. For example, this code will add two new entries to our "Open" menu (write this code in the Open Event of the App object):
openMenu.Name = "OpenMenu"
Var firstSubmenuItem As New MenuItem("Last File")
firstSubmenuItem.Name = "firstOpenSubmenuItem"
Var secondSubmenuItem As New MenuItem("Select File…")
secondSUbmenuItem.Name = "secondOpenSubmenuItem"
openMenu.AddMenu(firstSubmenuItem)
openMenu.AddMenu(secondSubmenuItem)
FileMenu.AddMenuAt(0, openMenu)
How to Set the Action to Menus Created from Code
We have already seen how easy it is to set the Menu Event to the statically created menu items; but, how can we assign the action that should be executed by a menu item created from code? We have two options for that, and probably the fastest and most versatile is using the AddHandler command. This command allows us to assign the Event execution to the Delegated Method of our choice.
The designated method has to provide the same amount and type of parameters that is expected by the original Event, where the first additional parameter will be the one representing its own type of the Sender object (the one whose Action event we are substituting, MenuItem in this case). The designated method also has to return the same type as the original Event, if this is the case (Boolean for the MenuItem Action).
In order to see how this works, add a new method to the App object using the following signature:
- Method Name: ActionMethod
- Parameters: Sender as MenuItem
- Return Type: Boolean
- Scope: Public
Next, write the following code in the associated Code Editor for the Method:
Let's modify our previous example code to assign the action executed by every submenu entry:
openMenu.Name = "OpenMenu"
Var firstSubmenuItem As New MenuItem("Last File")
firstSubmenuItem.Name = "firstOpenSubmenuItem"
Dim secondSubmenuItem As New MenuItem("Select File…")
secondSUbmenuItem.Name = "secondOpenSubmenuItem"
AddHandler firstSubmenuItem.Action, AddressOf ActionMethod // We assign the Delegate Method as the Action to execute
AddHandler secondSubmenuItem.Action, AddressOf ActionMethod // We assign the Delegate Method as the Action to execute
openMenu.AddMenu(firstSubmenuItem)
openMenu.AddMenu(secondSubmenuItem)
FileMenu.AddMenuAt(0, openMenu)
Run the example app. You'll notice that now the submenu items are active because they have their Action set. Try to select these menu items in order to see how it executed the code from the Delegate Method. In addition, it is possible to assign the same delegated method as the Action for several menu items. The second option we have is to create our own subclasses based on MenuItem and implement the Action Event on these with the code they have to execute. Then, we just need to create the new menu item instances from our subclasses. On one hand, the main advantage of this approach is the reuse and encapsulation it provides, while in the other hand it is less flexible and versatile when compared with the AddHandler approach.
Let's see the subclass approach in action applied to our example.
First, add a new Class to the project and use the following data in the Inspector Panel for the added class:
- Name: MySubMenuItem
- Super: MenuItem
When done, you will notice in the Project Browser that the icon displayed for our new subclass changes to show the one associated with the MenuItem instances. With our new subclass still selected, access the contextual menu and select the Add to "MySubMenuItem" > Event Handler… option. Choose the Action entry in the resulting window and write the following code in the associated Code Editor:
Return now to the Open Event of the App object and modify the code so it makes use of the new subclass:
openMenu.Name = "OpenMenu"
Var firstSubmenuItem As New MySubMenuItem("Last File") // We create the instance from our MenuItem subclass, using the inherited Constructor
firstSubmenuItem.Name = "firstOpenSubmenuItem"
Var secondSubmenuItem As New MySubMenuItem("Select File…") // We create the instance from our MenuItem subclass, using the inherited Constructor
secondSUbmenuItem.Name = "secondOpenSubmenuItem"
openMenu.AddMenu(firstSubmenuItem)
openMenu.AddMenu(secondSubmenuItem)
FileMenu.AddMenuAt(0, openMenu)
Run the example app again and you will notice that the submenu entries will run their events, providing the same result.
How to Hide and Destroy Menu Items: Visible, Remove and Close
Now you know how to create and add static and dynamically created menu items, even how to provide the code executed in response to user selection; but, how do you hide or destroy menu items displayed in the menu bar?
First, let's differentiate between not seeing and removing a menu item from a menu. The Visible property determines exactly that: if the affected menu item will be visible or not as part of the menu hierarchy; in this case the object still will be available for use (and reference) during the app execution. That is, we still will be able to reference a menu item whose Visible property has been set to False. On the other hand, when we remove a menu item from a menu hierarchy, we will be destroying the object from memory, so we will not be able to reference it in the future. If we need the functionality provided by a removed menu item, then we will need to create a new instance from scratch.
The MenuItem class (and, thus, all the subclasses derived from it) offers two methods (with variants) we can use to remove (destroy) menu items: Remove and Close. The first method is available in two variants: the first variant accepts as parameter an Integer value representing the zero based index for the menu item we want to remove; while the second version of the method expects to receive as its parameter a reference to the menu item to remove. For example, these two pieces of code will provide the same result, removing our secondSubmenuItem from the menu hierarchy. In order to make this a bit more interesting, add a new button to the example project window and write the following code into its Action Event:
If sourceMenu <> Nil then sourceMenu.RemoveMenuAt(1) // We remove the MenuItem placed in the second position of the submenu hierarchy
If you don't want to deal with index values, you can use the second version of the Remove method:
If sourceMenu <> Nil then sourceMenu.RemoveMenuAt( sourceMenu.Child("secondOpenSubmenuItem") ) // We remove the MenuItem whose object name matches the provided as parameter
In addition, we also can use the Close method. For example, this will have the same effect in our submenu:
If sourceMenu <> Nil then sourceMenu.Child("secondOpenSubmenuItem").Close // We Close (or remove) the MenuItem whose object name matches the provided as parameter.
Display Menus…Anywhere!
The MenuItem class provides a really powerful method: PopUp. This method allows us to display any MenuItem, including complete menu or submenu hierarchies, in any position inside a RectControl object, providing for that the `X` and `Y` coordinates as pixels values. When combined with the Clone method we can implement menus for practically any control under any circumstance. In addition, the PopUp method will return as result the MenuItem entry selected by the user, if any.
With the following example we will put this feature in action, displaying our OpenMenu every time we click on the app window. So, let's start adding the MouseDown event to the default Window1 object of the App. Next, write the following code in the associated Code Editor:
If menu <> Nil Then
Var selection As MenuItem = menu.Clone.PopUp(x, y) // We clone the menu and display it in the same coordinates where the user clicked
If selection <> Nil Then MessageBox(selection.Value) // We verify if the user has selected a menu item option, displaying its text if is a valid object
End If
Return True
Enabling and Disabling Menu Items
As we have seen, the `MenuItem` class uses the AutoEnable property with the True value by default, so the instances will be displayed as selectable they always have an associated Menu Handler or Delegated Method. However, you will probably want to decide on the fly which menu items should be active based on several conditions and variables. This is something we can do implementing the EnableMenuItems Event Handler in any of the project Windows that should decide about the menu items of their associated menu bars, the App object and, of course, the ContainerControls.
In order to see this feature in action, let's add our own feature to copy text that will be enabled only if there is text to copy in a text field added to the default window of the project. First, we will add our own "Copy Text" menu item under the Edit menu that is available in the default menu bar of the project. Write this code in the Open Event of the Window1 object:
MyCopyTextVersion.Name = "MyCopyTextMenuItem"
MyCopyTextVersion.KeyboardShortcut = "Cmd-Shift-C"
AddHandler MyCopyTextVersion.Action, AddressOf ActionMethod
EditMenu.AddMenu(MyCopyTextVersion)
Of course, we need to add the ActionMethod delegated method we already used in a previois example, but for the Window1 object in this case. Here is where we will execute the action. Create the ActionMethod using this signature:
- Method Name: ActionMethod
- Parameters: Sender as MenuItem
- Return Type: Boolean
- Scope: Public
And write the following code in the associated Code Editor:
Add now a new TextField control to the Window1 layout. Next, add the EnableMenuItems Event to the Window1 window and type the following code in the resulting Code Editor:
If sourceMenu <> Nil Then sourceMenu.Enabled = TextField1.Value <> "" // We enable the menu item only if there is text in the TextField.
Run the app and you will notice that menu option is always disabled when the text field is empty, and enabled when you put some text in the text field. When we select the menu item (or use the associated keyboard shortcut), then the textfield text will be copied to the clipboard, so you can paste it into any app that accepts text.
Add now the EnableMenuItems Event Handler to the App object and type this code:
Run the app again and now you will notice that the menu item is always active no matter if the textfield has text or is empty. This is because the event chain responsibility executes the handler on the frontmost active window (if implemented) first, and then the one available in the App object (if implemented). Thus, double check for this when dealing with the EnableMenuItems handlers for your menu items!