The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Fun With Menus

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
James Robertson

Posts: 29924
Nickname: jarober61
Registered: Jun, 2003

David Buck, Smalltalker at large
Fun With Menus Posted: May 12, 2007 12:02 AM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: Fun With Menus
Feed Title: Pollock
Feed URL: http://www.cincomsmalltalk.com/rssBlog/pollock-rss.xml
Feed Description: Pollock - the next VW GUI
Latest Agile Buzz Posts
Latest Agile Buzz Posts by James Robertson
Latest Posts From Pollock

Advertisement

Today we present the basics of working with Menus on the MenuBar of a window.

Design

We'll create a Menu with three menu bar items, which we'll call File, System and Other. We'll add sub menus to these items, which become the drop downs from the main menu bar items.

Here's our working class:

	Smalltalk defineClass: #MenuWork
		superclass: #{Widgetry.UserInterface}
		indexedType: #none
		private: false
		instanceVariableNames: ''
		classInstanceVariableNames: ''
		imports: 'private Widgetry.*'
		category: '(none)'

Notice here that we don't have any instance variables. Once we create our menu, it is automatically accessible via the #menuBar message on our UserInterface. In a real (real as in more than just our examples, i.e. real work) UserInterface, adding instance variables for the various panes makes it easier for us to access them. Cluttering up a UserInterface with menu bar menus in instance variables isn't really helpful.

You Can't Get There From Here

UserInterfaces have two special methods that get called when they are opening that help us with our main menu work. The first is #getMainMenu. In this method, your job is to answer a Menu object, which will then be used for the MenuBar. The second is #hookupMainMenu, which is where you can manipulate the main menu, adding actions or configuring menu items.

So, let's start with our basic #getMainMenu:

	getMainMenu

		| menu |
		menu := Menu new.
		menu addMenuItem: (MenuItem label: '&File').
		menu addMenuItemWithLabel: '&System'.
		menu addMenuItemWithLabel: '&Other'.
		^menu

And here is what it looks like when we execute MenuWork open:

I included two ways of adding a menu item above. The first one, for our File menu item was by creating a MenuItem, and then calling #addMenuItem:. The second is calling the helper method #addMenuItemWithLabel:, which does the same thing under the hood, creating a MenuItem and then calling #addMenuItem:

Open House

Just like with DisplayLabels, putting a & in the string automatically turns the following character into a hot key. If there was something to open for these, then pressing Alt-F, Alt-S or Alt-O would open those submenus. You'll also see the proper fly over behavior when you move the mouse over one of the menu bar items. Clicking on a menu bar item does nothing at this time, because we need to add sub menus to these items.

So, let's start by adding some items to each menu bar items:

	getMainMenu

		| menu submenu subsubmenu |
		menu := Menu new.
		menu addMenuItem: (MenuItem label: '&File').
		menu addMenuItemWithLabel: '&System'.
		menu addMenuItemWithLabel: '&Other'.
		submenu := Menu new.
		submenu addMenuGroupFromLabels: #('Inspect' 'Browse').
		submenu addMenuItemWithLabel: 'Close'.
		(menu menuItemAt: 1) submenu: submenu.
		submenu := Menu new.
		submenu addMenuItemWithLabel: 'Toggle Me'.
		submenu addMenuItemWithLabel: 'Submenu'.
		(menu menuItemAt: 2) submenu: submenu.
		subsubmenu := Menu new.
		subsubmenu addMenuItemWithLabel: 'Do Nothing'.
		(submenu menuItemAt: 2) submenu: subsubmenu.
		submenu := Menu new.
		submenu addMenuItemWithLabel: 'Red'.
		submenu addMenuItemWithLabel: 'Green'.
		submenu addMenuItemWithLabel: 'Blue'.
		(menu menuItemAt: 3) submenu: submenu.
		^menu

And here is what the resulting menus look like when they're activated:

Simply setting a menu with #submenu: to a menu item makes it do the right there.

Note that we added a group, as we see in the first "File" menu picture. Whenever we add a group, the resulting menu will automatically add a menu separator after that group. In fact, under the hood, it takes that group of menu items, and creates a Menu from them, and adds that menu.

In this way, menus are created from Menus and MenuItems. You can mix and match these in a Menu, and even nest Menus in Menus in Menus and so on.

The system under the hood that reads your Menu, and builds menu bars and menu panes, looks at the structure you have given it, and always inserts separators for you, either:

  • Between Menus in the Menu
  • Between a MenuItem and a following Menu
  • Between a Menu and a following MenuItem

Sad Face Emoticon

Before we move on, I want to point out that while #menuItemAt: is a valid message to send to a menu, its really not the best way of coding. Like Panes, MenuItems can have IDs. It is better if we give menu items IDs from the get go, and instead use the #menuItemWithID: or #menuItemWithID:ifNone: methods. That way, if later, you start modifying a menu on the fly, adding items or removing items, provided that the ID you want is still there, it will find it. Banking on indexes really isn't good practice.

So, let's change it all, by adding IDs up front, and using #menuItemWithID::

	getMainMenu

		| menu submenu subsubmenu menuGroup |
		menu := Menu new.
		menu addMenuItem: ((MenuItem label: '&File') id: #File).
		menu addMenuItem: ((MenuItem label: '&System') id: #System).
		menu addMenuItem: ((MenuItem label: '&Other') id: #Other).
		submenu := Menu new.
		menuGroup := Menu new.
		menuGroup addMenuItem: ((MenuItem label: 'Inspect') id: #Inspect).
		menuGroup addMenuItem: ((MenuItem label: 'Browse') id: #Browse).
		submenu addMenuItem: menuGroup.
		submenu addMenuItem: ((MenuItem label: 'Close') id: #Close).
		(menu menuItemWithID: #File) submenu: submenu.
		submenu := Menu new.
		submenu addMenuItem: ((MenuItem label: 'Toggle Me') id: #ToggleMe).
		submenu addMenuItem: ((MenuItem label: 'Submenu') id: #Submenu).
		(menu menuItemWithID: #System) submenu: submenu.
		subsubmenu := Menu new.
		subsubmenu addMenuItem: ((MenuItem label: 'Do Nothing') id: #DoNothing).
		(menu someMenuItemWithID: #Submenu) submenu: subsubmenu.
		submenu := Menu new.
		submenu addMenuItem: ((MenuItem label: 'Red') id: #Red).
		submenu addMenuItem: ((MenuItem label: 'Green') id: #Green).
		submenu addMenuItem: ((MenuItem label: 'Blue') id: #Blue).
		(menu menuItemWithID: #Other) submenu: submenu.
		^menu

Several things going on here. First we note that there is more code we have to write. But in doing so, we get more easily read code, much better at revealing exactly what we're trying to do.

Next we create a menu group, which in fact is only just a menu, and add it to the first submenu. We use the standard #addMenuItem: method. No matter if we are adding a single menu item, or a menu, we always simply use #addMenuItem:. Under the hood, Menus are made up of menu parts. A menu part can be either a menu or a menu item.

Finally, we used the much more readable #menuItemWIthID: methods to add the first level menus, but, we used #someMenuItemWithID: for adding the Submenu's submenu. We could have written it as:

	(submenu menuItemWithID: #Submenu) submenu: subsubmenu.

The difference in these two methods is how deep the search goes. #menuItemWithID: only searches the menu itself, it doesn't search any submenus in that menu. #someMenuItemWithID: searches the whole tree of the menu looking for a matching ID. You use the one that makes the best sense depending on your situation.

Lights, Camera... Extra Credit

We'll finish off today by hooking up some actions to our menu. We do this with Actions which we've seen before. We use the #hookupMainMenu method to configure our menu.

	hookupMainMenu

		| menuBarMenu |
		menuBarMenu := self menuBar menu.
		(menuBarMenu someMenuItemWithID: #Close) actionID: #closeMe.
		self addAction: (Action
			id: #closeMe
			action: [self close]).
		(menuBarMenu someMenuItemWithID: #Inspect) actionID: #inspect.
		self addAction: (Action
			id: #inspect
			action: [self inspect]).
		(menuBarMenu someMenuItemWithID: #Browse) actionID: #browse.
		self addAction: (Action
			id: #browse
			action: [self browse])

Some things to note here. First, we send menuBar menu, which deserves a bit of explanation. Once #getMainMenu is called, the actual menu bar is created and added to the window. The menu it uses is in the #menu method of a menuBar. From there on, no actual menu panes are created until you actually open a menu (or submenu). Having made sure that all my IDs were unique, I can simply use the #someMenuItemWithID: method to find the menu items we want to modify, and give them an action ID. Like with RadioButtons we can use any kind of ID, basically any object that is able to be uniquely identified. Unlike with the tricks we used with RadioButtons, there is no need to get fancy, so symbols do nicely.

Actions are used in three places. As we saw with the TextEdit and InputField, they can be used to attach behavior to a #doit. They are also used to associate a keystroke with behavior, for use as HotKeys. Finally, for menus, they are used to associate a menu item being selected with an action.

Note: It is not important which order things are written here. You could add all the actions first, then set the actionIDs, or anything in between. The ids and the actions are loosely coupled. This allows you to do whatever you want up until the last moment, when a menu item is selected. If an id doesn't match any action's ID, it simply quitely does nothing.

Finally, we note that we add the actions to self. For the menuBar, the lookup order for an action goes to your UserInterface. Each pane, including the main window, as well as your UserInterface has a set of actions local to it. Some default actions are automatically added when you add a main menu. If you inspect your MenuWork instance, you'll see that there are some besides the ones we added in the actions instance variable. These are actually hooked up to the left and right arrow. Thus, we don't actually hard code what those keystrokes do, but rather use actions to do the work for us.

And so, if you select these menu items, you'll be able to either close, inspect or browse the MenuWork.

Isn't that pretty?

And So It Goes
Sames

Read: Fun With Menus

Topic: DevSummit 2007 Previous Topic   Next Topic Topic: Why Project Management Tool Demands Integrated Help Desk?

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use