Search code examples
javamacosswingmenubarjmenubar

What is the best way to make an OS X screen JMenuBar work consistently across windows?


I have a cross-platform Java application with a Swing user interface. On OS X, the application uses the screen menu bar for a more native user experience.

In general, the application creates one JFrame per document. The screen menu bar must remain consistent across all of these windows. I have tried several ways of doing it, and found only one consistent and performant solution, which is adequate but not perfect. I am posting this question in case anyone else has a better approach, and with the hope that this information helps others.

Some approaches that do not work:

Attach same menu bar to multiple windows

I tried adding the same JMenuBar to multiple JFrame instances, but Swing only supports a JMenuBar being attached to a single JFrame at a time, even as a screen menu bar.

I also tested with an AWT MenuBar rather than a JMenuBar, but the same phenomenon occurs. And MenuBar has many limitations compared to JMenuBar (e.g., no icons), so let's proceed with the requirement that we want a JMenuBar.

Clone the menu bar

One common solution is to create a copy of the JMenuBar for each new JFrame. However, there are at least two problems with that. First, you must keep the menu bars in sync. While you can use listeners to do it, it is a lot of extra code just to handle the OS X platform. However, the second and more serious issue is performance: if you have a complex menu bar with hundreds of menu items, cloning the menu bar is very slow. We found that this approach delayed the appearance of new windows by several seconds!

Use a default menu bar

A new method was added to Apple's Java library in Java for OS X v10.6 Update 1 and 10.5 Update 6: Application.setDefaultMenuBar(JMenuBar).

The stated purpose of this method is to provide a menu bar when no JFrame is active, but it also shows the default menu bar when a JFrame with no JMenuBar of its own is active.

However, there are several major problems with the setDefaultMenuBar functionality:

  1. Accelerators do not work. I avoided this problem in our application by handling all key presses ourselves, but it is still unfortunate.
  2. As of December 2012, setDefaultMenuBar was still not available on Java7. We obviously want to avoid using deprecated or unsupported APIs.
  3. Most critically, calling setDefaultMenuBar prevents the JVM from shutting down properly. Even a subsequent call to setDefaultMenuBar(null) does not free the necessary resources.

In short, setDefaultMenuBar does not seem like a safe and robust way to go at all.

So, the question is: What is the most reliable, performant and compatible (across versions of OS X) way to implement a consistent screen JMenuBar?


Solution

  • You may be able to leverage JDialog, which inherits its parent's JMenuBar. To keep the dialogs modeless, you can use

    • PropertyChangeEvent to communicate among the dialogs and the main JFrame, as suggested here.

    • Action and Key Bindings to navigate among the dialogs.