by Aaron Ballman

So you want to do some sort of dynamic menu system for your application such as an Open Recent or Window menu, but you're not certain where to start or what to do? Then you've come to the right place! This article is going to discuss the "new" MenuItem API in REALbasic. It's possible that you're familiar with the old-style MenuItem APIs, which are also called "menu item arrays." If that's the case, then you should still read this article since you will learn the new, cleaner way to design your menus in REALbasic.

Before we begin diving right into code, let's take a bit to discuss what problems we're trying to solve, and why the new-style APIs are the best solution.

The dynamic MenuItem APIs are a new (at least, as of 5.0) set of ways to manipulate menus. The basic principle behind the APIs is that a menu is nothing more than a tree structure of data that is attached to a particular window. This is quite similar to the way a file system works, and so the APIs you will use are going to look a lot like the FolderItem APIs you're already used to. So there's a root MenuItem (also known as the menu bar), and it has child menus which represent the top-level menu items (like File and Edit), which in-turn contain child menus (such as Exit or Cut). Any MenuItem which has children is usually called a "menu" or "sub-menu", and any MenuItem which fires an action is just called a "menu item." If you're already comfortable with FolderItems, then a menu is simply like a folder and a menu item is like a file.

While you can certainly use these new APIs to construct an entire menu bar for a window, that tends to be a very tedious process. So what are they good for? What I like to call "repetitive motions" -- any time you find that you have a bunch of menu items which all do the same action, but you're not certain how many items are needed, that's the perfect time to use the new MenuItem APIs. As I mentioned before, an Open Recents menu is a perfect example. You're not certain how many recent items there will be in the list, but each item does the same action -- it opens a file. So let's make an Open Recents menu for our hypothetical application so you can see how things work.

The first things that you'll probably be wondering about are "where do I put my action code" and "how do I do this in the menu editor?" I'll answer these in reverse order. The menu editor isn't involved that much because this is a dynamic menu. So you just need to create a place for all these special type of items to go. In our example, we need a sub-menu to be created under File whose text is "Open Recents" and name is "FileOpenRecent." Now, if you're used to the menu item array style of menus, you'd think "and then I need to make a hidden menu item under our new sub-menu." That's a hack, so resist the urge!

Instead, we're going to make a MenuItem subclass which handles all the logic behind our open recents menu. This neatly answers the question of "where do I put my code" as well. In the project window, create a new class and name it OpenRecentMenuItem. Then set it's super to MenuItem. If you go into the code editor for our new class, you'll notice that there are two events in it: Action and EnableMenuItems. Each instance of a MenuItem has the chance to decide when it should be enabled and what to do when the item is selected. We can ignore the EnableMenuItems event since our items will always be enabled (thanks to the MenuItem.AutoEnable property defaulting to true, which tells REALbasic to enable a particular menu whenever there's a handler for it).

So what sort of actions can our menu items take? Well, whenever the user selects an item from the Open Recents menu, it should try to open that document up. So we'll need one piece of information -- a FolderItem to open. For the sake of this article, we're going to assume that there's a method called App.OpenFile which takes a FolderItem and opens it.

So add a new property to the OpenRecentMenuItem class called "mFile as FolderItem", and make it a private property. Then, add a new method named Constructor which takes a FolderItem as its only parameter. The constructor will then be responsible to set up the way the menu item looks. Finally, we want to fill in the action event with the proper code. The OpenRecentMenuItem class should look like this when you're done:

Class OpenRecentMenuItem
  Sub Constructor( item as FolderItem )
    // Store the file to open
    mFile = item

    // This call is redundant, but it's nice to be explicit
    me.AutoEnable = true

    // Set the view for the menu item
    me.Text = item.DisplayName
  End Sub

  Event Action() as Boolean
    // Fire the open method with our stored folder item
    App.OpenFile( mFile )

    // We handled the action
    return true
  End Event
End Class

As you can see, our class is pretty straightforward. It sets the menu item up to have a decent looking display text, and whenever the item is selected, it tries to open the file up.

So now that we have some generic code to work for our dynamic menu, let's take a look at how to set the menu itself up. Before we get into code, let's talk a bit more about the MenuBar property of a Window or App class. Since applications can have multiple windows, and each window can have a menu bar on it, REALbasic had to devise a scheme to make everything work properly on all platforms. So, here's a few things to keep in mind when it comes to designing your menus -- the App class refers to a MenuBar for the entire application. This makes sense in two cases -- when compiling for the Mac, and when making an MDI application on Windows. If you're not doing either of these things, then you're more interested in the MenuBar property of the window. The default application abstracts these details from you a bit by setting the App.MenuBar and Window1.MenuBar properties to the same value. This means that when you compile for Windows, the Window.MenuBar property tells the window which menu bar to attach to it, and on the Mac, the App.MenuBar property is used. Since they both point to the same MenuBar, they have the same behavior on both platforms. Also, since they both point to the same MenuBar object, any changes made to the menu bar in one item changes the menu bar in the other. We're going to exploit this fact and change the App.MenuBar -- but you should understand why that is affecting the Window.MenuBar now.

So in our App class, we're going to construct the Open Recent menu from scratch. For the sake of focusing on menus, we're going to assume that there is a helper function that saves and reloads the recent item information from a preferences file somewhere. The basic idea is a mix of folder items and arrays. We're going to obtain a handle to our parent menu, and then begin adding children to it.

Sub ConstructOpenRecents()
  // Get a handle to our parent sub menu.
  Dim parent, child as MenuItem
  parent = App.MenuBar.Child( "FileMenu" )	// Get the file menu
  if parent = nil then return

  parent = parent.Child( "FileOpenRecent" )	// Get the open recent submenu
  if parent = nil then return

  // Now that we have a handle, let's set each of our menu items
  dim count, i as Integer
  count = NumberOfOpenRecentItems()

  for i = 0 to count - 1
  	// Construct our child item
  	child = new OpenRecentMenuItem( GetOpenRecentFile( i ) )

  	// And add it to our sub-menu
  	parent.Append( child )
  next i
End Sub

That's all there is to it! The first few calls are a way for us to get a reference to the proper sub-menu (remember, it's the one we added in the menu editor). We use MenuItem.Child to traverse the menu bar like a tree, much like you would with a FolderItem. Once we've found the proper parent, we simply call MenuItem.Append to add our newly created children. Since we already wrote the Action event code for the OpenRecentMenuItem class, our Open Recent menu is finished.

You may be wondering why I am using .Child( "FileMenu" ) instead of just using FileMenu directly. That's because I like being explicit about which menu bar and item I am dealing with. Referencing menus by name is a neat trick that's made possible by some REALbasic magic. However, whenever magic is involved, there are places to watch out for (the same is true of things like referring to a window by name instead of via a variable), so I am being explicit. When you refer to a MenuItem by name, it will only work for the currently "installed" menu bar at design time.

Let's say you are writing code in the App class and you say FileMenu.Text = "Bleh". The FileMenu is a magic reference back to the FileMenu on the App's MenuBar. So if App has no MenuBar set, or its menu bar has no child named FileMenu on it, your code won't work. I've run into cases where I'm writing code in Window1 where I am trying to change a menu on Window2, which has a different menu bar. In this case, Window1 cannot use this magic to refer to items on Window2. So I try to be explicit about which menu I'm dealing with, and snag all my menu references manually. I've found that it saves me a headache in the long run.

Now that you have more of a feel for how to properly use the new menu system, let's talk a bit more about the actual API itself. I'm going to go thru some of the major new changes and briefly describe each API and what it's good for.

  • Append( item as MenuItem )
  • As you saw above, this call will append the given item onto the end of the parent item. It works much the same as appending items onto the end of an array.
  • Insert( index as Integer, item as MenuItem )
  • This is the sibling function to Append, and you guessed it -- it inserts the given item into the menu at the 0-based index specified. You probably won't use this API too much when dealing with truly dynamic menus (since it's tough to gauge the index if the menu is dynamic). But it's useful when trying to add items to the "front" of the list instead of the back (since you can just continually specify the index as 0).
  • Remove( index as Integer )
  • Remove( item as MenuItem )
  • Of course, since you can add items at runtime, you need a way to remove items at runtime as well. There are two different ways you can do it, either by index (such as when removing all items from a menu, but leaving the menu itself), or by item (such as when removing an entire menu). When making things like a Window menu, this API will come in handy.
  • Child( name as String ) as MenuItem
  • Child( text as String ) as MenuItem
  • As you saw in our example, you can find a child of a particular MenuItem using this API. You can find the child either by name (such as we did), or by its text. I suggest you always refer to an item by name when possible since names are unique (and text doesn't have to be). If you have two items on the same menu which share the same text, the first instance found is returned.
  • Item( index as Integer ) as MenuItem
  • Just like with FolderItems, if you don't know the name or text of an item, you can always find it by index as well. This is the best API when needing to loop over every menu item in a menu.
  • Count() as Integer
  • This API tells you how many children a menu owns. Terminal menu items (ones that fire an action) will have a Count of 0. As you have probably guessed, this API is usually used closely with the MenuItem.Item API.
  • AutoEnable as Boolean
  • This new property deals with the enabled state of the menu item. In older versions of REALbasic, you were responsible for manually enabling menu items as you saw fit. However, now you can set the AutoEnable property to true and REALbasic will automatically enable the menu item if it can find a menu handler for the item. This is very handy for any menu item that stays enabled all the time (such as File->New or File->Open).

By now you should have a more firm understanding of how the new MenuItem APIs work and should be able to use this knowledge to design your own menu systems properly. If you have any questions, feel free to ask me at aaron@aaronballman.com. You can download the example project discussed in this article here.