[an error occurred while processing this directive]
> developer > web app development
Using Swing Menus in SilverStream Forms
by Alex Rosen, Principal Engineer, Novell
Date Created: 2000-04-21 11:52:00.000
  Swing Menus vs. AWT Menus
  Using Swing Menus
  Issue One: Hidden Menus
  Issue Two: Sizing Problems
  Issue Three: Responding to Menu Selections
  Issue Four: Heavyweight Controls
  Issue Five: Repaint Problems with Dialogs
  Sample Code

Swing menus vs. AWT menus
The Swing package in JDK 1.2 introduces an entirely new set of menu components: JMenu, JMenuItem, JMenuBar, and so on. Like other Swing components, such as JButton and JTextField, these menu components are drawn entirely by Swing, without using any "real" menu components from the underlying operating system. This means that their behavior is more consistent across platforms, and since all the functionality is provided by Swing instead of the underlying OS, Swing menus are not limited to lowest-common-denominator functionality. They provide many nice features that are lacking in regular AWT menus: complete shortcut and accelerator support, icons, complete customizability, etc.

However, menu behavior is surprisingly complex to implement, and the current Swing menus haven't gotten it completely right yet. They feel a little less crisp than AWT menus, and they have some subtle behavior problems.

Here's an example of one of those behavior problems, involving JMenu's focus handling. Suppose your application contains some JTextFields, and your JMenuBar contains a standard Edit menu, and you'd like to let your users select the Paste menu item to paste text into the text fields. When the user clicks in the JMenuBar, the JMenuBar receives focus, and the current JTextField loses focus. This is different from AWT MenuBars, which do not take focus. This means that, if there is a validation rule on the JTextField, it will fire prematurely. What's worse, after the user selects the Paste menu item, focus returns to the JTextField. However, this causes the text field to select all of its text. Then, when you handle the Paste command by calling JTextField.replaceSelection() with the clipboard contents, the entire field is replaced, instead of just the original selection. For example, if the caret was originally at the end of the text, then the text on the clipboard will replace the entire text in the field, instead of being appended to the end of it.

using Swing menus
For these reasons, SilverStream currently recommends using the original AWT menus instead of the new Swing menus. The SilverStream Designer uses AWT menus, and AgfForm does not provide the support for Swing menus that it does for AWT menus.

However, many SilverStream users want the new functionality of the Swing menus, and have tried to use them in SilverStream, without much success. The rest of this article describes how to successfully use Swing menus in a SilverStream form running in SilverJRunner.

issue one: hidden menus
The obvious way to add a JMenuBar to a form running in JRunner would be to add the menu bar to the JFrame containing the form:

JFrame f = (JFrame)getFrame();

f.setJMenuBar(menuBar);

But if you do this, you won't see your menus. Here's what's going on:

Because SilverStream forms are designed to run in either JRunner or in a browser, they are always placed in a JApplet component before being displayed. In the browser, this JApplet is placed directly in the HTML page; in JRunner, it is placed in a JFrame. Because JMenuBars and JMenus are lightweight components, if you add them to a JFrame, they will be obscured by the heavyweight JApplet. (See http://java.sun.com/products/jfc/tsc/articles/mixing/index.html for more information on mixing lightweight (Swing) components and heavyweight (AWT) components.)

The workaround is to add the JMenuBar to the JApplet instead of the JFrame. Starting with the form, search up the component hierarchy until you find a JApplet component, and add the menu bar to it. See formActivate() and getJApplet() in the sample code below.

issue two: sizing problems
Having worked around Issue One, you'll soon discover that your form is cut off on the bottom in JRunner, when you add a JMenuBar. The problem is that formActivate() is being called from addNotify(), which is called from the pack() method of the JFrame that the form is in. The pack() method is in the middle of trying to determine the right size for the window, and adding the JMenuBar in the middle of this will confuse it, so it doesn't leave enough room for both the menu bar and the form. Thus, the menu bar pushes the form down, and the bottom of the form gets clipped.

The workaround is for the form to lie about its true size, to force the pack() method to leave enough room for both the menu bar and the form. We do this by overriding the form's getSize() method, and returning a size that is taller (by increasing the height dimension by the preferred height of the menu bar). This getSize() method is called before the formActivate() method. Once the formActivate() method is called and the JMenuBar has been added, we can stop lying and return the form's real size, since the JMenuBar is now around to reserve room for itself. See the getSize() method in the sample code below.

issue three: responding to menu selections
SilverStream provides an easy mechanism for setting up AWT MenuItems and responding to their selection. The addMenuItem() and addCheckboxMenuItem() methods set up the form as a listener to a MenuItem, and the menuItemSelected and menuItemStateChanged events are automatically called when the menu item is selected. With JMenuItems, you must do these things yourself. It's easy to do this by adding the form as an ActionListener for each JMenuItem, and adding an actionPerformed() method to the form to handle the event that's fired when a JMenuItem is selected.

There's one important SilverStream-specific detail, however. SilverStream may generate the actionPerformed() method itself, if you write code on the actionPerformed event of a control on the form (such as a Button or JButton). This would cause SilverStream to overwrite your actionPerformed() code. To handle this situtation, you should instead write your menu handling code in this method:

private void handle_otherControls_actionPerformed(ActionEvent event)

Then, write a regular actionPerformed() method that calls this handler method. If SilverStream ever needs to write its own actionPerformed() method, it will overwrite yours, but it will still know to call the handle_otherControls_actionPerformed() method if it exists. The handle_otherControls_actionPerformed() method will never get overwritten. See the example of this technique in the sample code below.

(If your form already has a SilverStream-generated actionPerformed() method on it, just leave it - SilverStream will add the call to your handle_otherControls_actionPerformed() method when you save the form.)

issue four: heavyweight controls
(If your form already has a SilverStream-generated actionPerformed() method on it, just leave it - SilverStream will add the call to your handle_otherControls_actionPerformed() method when you save the form.)

By default, JMenus are lightweight components. As we mentioned before, that means that they will be obscured by any heavyweight components on your form. A menu that shows up behind a TextField on your form looks very disconcerting and bad. If you have any heavyweight (AWT) controls on your form, you can force the JMenu to use heavyweight components, which will enable them to be displayed above heavyweight controls, instead of underneath them. You do this by calling the static method JPopupMenu.setDefaultLightWeightPopupEnabled(false) before creating the menus. (When a menu is activated, JMenu uses a JPopupMenu to display it.) See the formActivate() method in the sample code below.

(Note that this is a different issue from Issue One. Here we are talking about the drop-down part of the menu, which we can make heavyweight if we want. Issue One involves the menu bar and menu titles, which are always lightweight.)

issue five: repaint problems with dialogs
One last issue will bite you if you respond to a menu item selection by showing a dialog. If the dialog happens to obscure the JMenu that it was chosen from, then when the dialog is dismissed, the JMenu will still be visible, even though the menu has closed. This repaint problem is Javasoft bug number 4189244 (see http://developer.java.sun.com/developer/bugParade/bugs/4189244.html, free registration required). The easiest way to work around this problem is to manually call repaint() after displaying the dialog. See saveMenuItemSelected() and optionMenuItemSelected() in the sample code below.
sample code
Add this sample code to a form, to see a simple form that illustrates the workarounds to the five issues mentioned above.

	// Save the menu bar, so we can adjust the window's layout later.

	private JMenuBar     m_menuBar;


	// Save the menu items, so we can respond to their action events.

	private JMenuItem     m_saveMenuItem;

	private JMenuItem     m_optionMenuItem;


	// Remember whether or not the menu bar has been added yet.

	private boolean     m_menuBarWasAdded;


	// Override getSize(), to trick the pack() method into leaving enough

	// room for the JMenuBar. We lie about the true height of this form,

	// so that the window is tall enough for both the menu bar and the form.

	public Dimension getSize()

	{

		// Get the real size of this component.

		Dimension d = super.getSize();


		// Ensure that the JMenuBar has been created.

		createJMenuBar();


		// If the JMenuBar hasn't been added yet, we have to lie about our

		// size, so that there will be room for the JMenuBar when it gets there.

		if (!m_menuBarWasAdded)

		{

			Dimension d2 = m_menuBar.getPreferredSize();

			d.height += d2.height;

		}

		return d;

	}


	protected void formActivate()

	{

		// Only needed on forms that have heavyweight components.

		JPopupMenu.setDefaultLightWeightPopupEnabled(false);


		// Make sure that the menu bar has been created, just in case getSize()

		// hasn't been called yet for some reason.

		createJMenuBar();

		// Find the JApplet to add it to.

		JApplet applet = getJApplet();

		// Add the menu bar to the JApplet.

		applet.setJMenuBar(m_menuBar);

		m_menuBarWasAdded = true;

	}


	// Create the menu bar. We show off a few of the nice features of

	// Swing menus here.

	private void createJMenuBar()

	{

		// If we've already created it, do nothing.

		if (m_menuBar != null)

			return;


		// Create the JMenuBar.

		m_menuBar = new JMenuBar();



		// Create the JMenu.

		JMenu m = new JMenu("Test");

		m.setMnemonic('t'); // The first 'T' will be underlined.



		// Create the "Save" menu item and add it to the menu.

		KeyStroke saveAccelerator = KeyStroke.getKeyStroke(

						KeyEvent.VK_S, Event.CTRL_MASK);

		m_saveMenuItem = new JMenuItem("Save");

		m_saveMenuItem.setMnemonic('s'); // The first 'S' will be underlined.

		m_saveMenuItem.setAccelerator(saveAccelerator);

		m_saveMenuItem.addActionListener(this);

		m.add(m_saveMenuItem);



		// Create the "Option" checkbox menu item and add it to the menu.

		KeyStroke optionAccelerator = KeyStroke.getKeyStroke(

						KeyEvent.VK_N, Event.CTRL_MASK | Event.SHIFT_MASK);

		m_optionMenuItem = new JCheckBoxMenuItem("Option");

		m_optionMenuItem.setMnemonic('o'); // The first 'O' will be underlined.

		m_optionMenuItem.setAccelerator(optionAccelerator);

		m_optionMenuItem.addActionListener(this);

		m.add(m_optionMenuItem);



		// Add the menu to the menu bar.

		m_menuBar.add(m);

	}


	// Find the JApplet that this form resides in.

	private JApplet getJApplet()

	{

		Component c = this;

		while(!(c instanceof JApplet))

			c = c.getParent();

		return (JApplet)c;

	}


	// Handle the actionPerformed event. This method may get overwritten by

	// SilverStream, if you write code on the actionPerformed event of a

	// control on the form, but the generated code will still call your

	// handle_otherControls_actionPerformed() method.

	public void actionPerformed(ActionEvent evt)

	{

		handle_otherControls_actionPerformed(evt);

	}


	// This is called by SilverStream when any actionPerformed event is

	// caught by the form.

	private void handle_otherControls_actionPerformed(ActionEvent evt)

	{

		Object src = evt.getSource();

		// Handle "Save" menu item.

		if (src == m_saveMenuItem)

			saveMenuItemSelected();

		// Handle "Option" menu item.

		else if (src == m_optionMenuItem)

			optionMenuItemSelected();

	}



	// This is called when the "Save" menu item is selected.

	private void saveMenuItemSelected()

	{

		agDialog.showMessage("Save menu item selected.");

		// Work around Javasoft bug 4189244 - only needed after displaying

		// a dialog as a result of a menu item selection.

		repaint();

	}



	// This is called when the "Option" menu item is selected.

	private void optionMenuItemSelected()

	{

		boolean state = m_optionMenuItem.isSelected();

		agDialog.showMessage("Option menu item selected. State = " + state);

		// Work around Javasoft bug 4189244 - only needed after displaying

		// a dialog as a result of a menu item selection.

		repaint();

	}