Creating menus

Most apps have a Menu Bar. The location of the menu bar varies by platform. On Mac, there is only a single menu bar and it appears at the top of the screen. On Windows, each window can have its own menu bar. Linux can work either way, depending on the distribution.

When you create a desktop project, a default menu bar is added automatically, called MainMenuBar. For most applications this is usually sufficient. You create additional menu bars by adding them to your project using the Insert > Menu Bar command on the toolbar or menu.

Default menu bar

The default menu bar, MainMenuBar, is automatically set as the MenuBar for the App and for each new window that you create. The menu that appears for a window depends on the platform being used. Since each window on Windows has a menu bar, you must specify the MenuBar property on the window in order for a menu bar to appear. Linux is similar.

On Mac, if the MenuBar property for a window is not specified, then the App.MenuBar property is used for the menu instead.

Implicit instance

You may have noticed that you can refer to a MenuBar globally by its name. This is because an "implicit instance" is automatically created for you. If you use this global name, then you get the same MenuBar instance everywhere you use it. If you modify the MenuBar in code, the modification will appear everywhere the MenuBar is used.

If you would rather have separate instances of the MenuBar, you should assign it in code manually in the Window.Opening event:

Self.MenuBar = New MainMenuBar

With Mac apps, if a window does not have a MenuBar specified, then the window uses the MenuBar specified on Application.MenuBar. With Windows and Linux, if a window does not have a MenuBar specified, then the window displays without a MenuBar even if one is specified in Application.MenuBar.

Dynamic menus

Sometimes you may need to create a menu dynamically. Examples of this include a menu that shows recently opened files, a menu that shows the names of the currently open windows or a menu that shows the name of available fonts.

To do this you create a DesktopMenuItem subclass and use the AddMenu and AddMenuAt methods of the DesktopMenuItem class to add instances of your subclass to the menu bar. Here is an example that adds a Font menu. Create a new DesktopMenuItem subclass (called FontMenuItem). In its MenuItemSelected event add this code:

MessageBox("Selected Font: " + Self.Text)

Reminder: To create a DesktopMenuItem subclass, create a new class and set its Super to DesktopMenuItem.

Now you can use this subclass to create a font menu. In the Opening event of the default window, add this code:

mFontMenu = New DesktopMenuItem("Font")
MenuBar1.AddMenu(mFontMenu)

Var fCount As Integer = System.FontCount
Var fMenu As FontMenuItem

For i As Integer = 0 To fCount - 1
  fMenu = New FontMenuItem(System.FontAt(i))
  mFontMenu.AddMenu(fMenu)
Next

This code creates a new top-level menu called "Font" and adds to it a menu item for each font that is installed on your system. When you run the application and click the Font menu, you will see a list of all the fonts. Click on a font and a dialog appears telling you the name of the one you clicked.

Another way to create menus dynamically is to use a Menu Control Set, although the method described above (using Append) is preferred. To create a Menu Control Set, add an item to a menu and in the Inspector set its Index value to a 0 (normally it will be blank).

When you add the Menu Handler for this menu item you will now see that it has a parameter: index As Integer. You can use this to tell which menu item was selected when there are multiple ones.

When you create a new instance of this menu item it will add a new entry to the menu in which it is contained. For example, if you have called the menu item WindowItem then you can use this code to add another entry to the menu:

Var m As New WindowItem
m.Text = "Menu Text"

Creating a Recent Items menu

You can use the above dynamic menus technique to create your own "Recent Items" menu. For example, the steps below describe how to create Recent Items menu for tracking recently opened files.

Create a class called "OpenRecentMenuItem" and set its Super to DesktopMenuItem.

In its MenuItemSelected event add some code to display the name of the file that was selected (it's stored in the Tag for the DesktopMenuItem):

 MessageBox("Recent Item: " + FolderItem(Me.Tag).NativePath)

Return True

In your MainMenuBar, add a menu to the File menu and call it "FileOpenRecent" and set its Text to "Open Recent" and its Submenu property to ON. With that in place, you can now add menu items to FileOpenRecent when a new file is open. As a test, you can add a button to a Window with code that lets the user select a file and then adds the file they selected to the FileOpenRecent menu:

' Choose a file
Var file As FolderItem = FolderItem.ShowOpenFileDialog("")
If file Is Nil Then Return

' Make one of our Open Recents subclasses and
' set it's text to being something (in this
' case, it's a number that we'll increment
' below)
Var recentItem As New OpenRecentMenuItem(file.NativePath, file)

' Put the latest item at the top of
' of the open recents menu on
' the File menu.
FileOpenRecent.AddMenuAt(0, recentItem)

To remove an item from the FileOpenRecent menu, just remove the last one in the list like this:

' If the recent menu has items, then remove the last one
If FileOpenRecent.Count > 0 Then
  FileOpenRecent.RemoveMenuAt(FileOpenRecent.Count - 1)
End If

You can find a working version of this in the Xojo examples here: Examples/Platforms/Desktop/Menus/Open Recent Menu

Contextual menus

Contextual menus are menus that appear when the user choose to see them. This is most often by right-clicking (or Ctrl-clicking on Mac) somewhere, but contextual menus can also be displayed using a keyboard shortcut on Windows.

All controls have two events that you use to create and handle contextual menus: ConstructContextualMenu and ContextualMenuItemSelected.

In ConstructContextualMenu, you can dynamically create the contextual menu (by appending menu items to the base parameter). Return True from the event to display the contextual menu:

base.AddMenu(New DesktopMenuItem("Test 1"))
base.AddMenu(New DesktopMenuItem("Test 2"))
base.AddMenu(New DesktopMenuItem("Test 3"))

Return True

In ContextualMenuItemSelected, you can test the selectedItem parameter to perform actions:

If hitItem <> Nil Then
  MessageBox(hitItem.Text)
End If

Return True

Special menus

There a few menu items that should have specific names in order for you to get automatic OS-provided functionality.

Help

If you add a Help menu, you should make sure its text property is set to just "Help" (or the localized equivalent) to allow Mac to automatically provide the Spotlight "Search" menu item that lets the user search all the menu items for specific text.

Edit

A top-level menu with the text "Edit" (or the localized equivalent), automatically get "Start Dictation" and "Insert Special Characters..." menus added on Mac.

Additionally, items in the EditMenu should also retain the names they are given by default if you want them to automatically work in TextFields, TextAreas and other controls. These names are: EditCut, EditCopy, EditPaste, EditClear, EditSelectAll, EditUndo and EditRedo. You can change their Text property, but do not change the Name property.

DesktopApplicationMenuItem

Desktop applications usually have an About menu that displays an About window with the name of the application and its copyright information. On Windows and Linux, this About menu appears in a Help menu. You can add a help menu by clicking the “Add Menu” button in the Menu Editor to add a new top-level menu. Name this menu "HelpMenu" and drag it so that it is rightmost. Now you can add the About menu item to the help menu.

On Mac, the About menu instead should appear in the Application menu rather than the help menu. To make it automatically move to the Application menu, you add your About menu to the Help menu so that it appears as expected for Windows and Linux. And then change its Super property in the Inspector from “DesktopMenuItem” to “DesktopApplicationMenuItem”. Any menu that uses this class will be automatically moved to the Application menu when it is run on macOS.

PreferencesMenuItem

Similarly, the Preferences menu is also located in the Application menu on Mac. On Windows and Linux, the Preferences menu is often located in the Edit menu and is instead called “Options”.

There is always a fixed Preferences menu in the Application menu on Mac, but is is disabled by default. To attach your Preferences menu to it, you set its Super property to “DesktopPreferencesMenuItem”. Only one menu in your project should be set to DesktopPreferencesMenuItem.

To change the name of the Preferences menu for Mac and Windows/Linux, you use a constant. By default there are several constants on the App class that control the text for Edit > Clear/Delete and File > Quit/Exit. You can add another to handle preferences.

To do so, add a new constant and call it kPreferences, setting its default value to "&Options...". In the Constant Editor, click the “+” to add a new entry and select “macOS” as the Platform. For the Value, enter "Preferences..."

This creates a constant that uses the value "&Options..." on Windows and Linux, but the value "Preferences..." when run on Mac.

Now that you have created the constant, you can use it as the text of the menu. Add a new DesktopMenuItem to the Edit menu and set its Text property to “#App.kPreferences”. This tells it to use the text of the constant. Also set its Super to “DesktopPreferencesMenuItem”.

You can use the preview buttons in the Menu Editor toolbar to see the text change between Mac, Windows and Linux. In addition, when you run the application on Mac, the Preferences menu appears in the Application menu instead of the Edit menu.