我有一个带有Swing用户界面的跨平台Java应用程序。在OS X上,应用程序使用屏幕菜单栏来获得更原生的用户体验。
通常,应用程序为每个文档创建一个JFrame
。屏幕菜单栏必须在所有这些窗口之间保持一致。我尝试了几种方法,只找到了一种一致且高效的解决方案,它是足够的,但不是完美的。我把这个问题贴出来,以防有人有更好的方法,希望这些信息能帮助别人。
一些不起作用的方法:
将相同的菜单栏附加到多个窗口
我尝试将相同的JMenuBar
添加到多个JFrame
实例,但Swing只支持JMenuBar一次附加到单个JFrame,甚至作为屏幕菜单栏。
我还用AWT MenuBar
而不是JMenuBar
进行了测试,但是发生了同样的现象。与JMenuBar
相比,MenuBar
有许多限制(例如,没有图标),所以让我们继续我们想要一个JMenuBar
的需求。
克隆菜单栏
一个常见的解决方案是为每个新的JFrame
创建一个JMenuBar
的副本。然而,这至少有两个问题。首先,您必须保持菜单栏同步。虽然您可以使用侦听器来实现,但仅处理OS X平台就需要大量额外的代码。然而,第二个也是更严重的问题是性能:如果您有一个包含数百个菜单项的复杂菜单栏,那么克隆菜单栏的速度非常慢。我们发现这种方法将新窗口的出现延迟了几秒钟!
使用默认菜单栏
Apple的Java库在Java for OS X v10.6 Update 1和10.5 Update 6中增加了一个新方法:Application.setDefaultMenuBar(JMenuBar)
.
此方法声明的目的是在没有JFrame
激活时提供一个菜单栏,但是当没有JMenuBar
激活的JFrame
也显示默认菜单栏。
然而,setDefaultMenuBar
功能有几个主要问题:
- 加速器不工作。在我们的应用程序中,我通过自己处理所有的按键来避免这个问题,但它仍然是不幸的。
- 截至2012年12月,setDefaultMenuBar在Java7上仍然不可用。我们显然希望避免使用不支持的api。
- 最关键的是,调用
setDefaultMenuBar
会阻止JVM正常关闭。即使随后调用setDefaultMenuBar(null)
也不会释放必要的资源。
简而言之,setDefaultMenuBar
似乎根本不是一种安全可靠的方式。
那么,问题是:实现一致的屏幕JMenuBar
的最可靠,性能和兼容(跨OS X版本)的方法是什么?
我发现的解决方案是通过向应用程序的每个窗口添加一个WindowListener
来监听windowActivated
事件。然后,将新激活窗口的JMenuBar
设置为我们想要显示的唯一菜单栏。
下面是一个例子:
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.WindowConstants;
/**
* On OS X, with a screen menu bar, you can "hot-swap" a JMenuBar between
* multiple JFrames when each is activated. However, there is a flash each
* time the active window changes, where the menu bar disappears momentarily.
* But it is a small price to pay to be able to reuse the same menu bar!
*/
public class HotSwapJMenuBarOSX {
public static void main(final String[] args) {
System.setProperty("apple.laf.useScreenMenuBar", "true");
final JMenuBar menuBar = new JMenuBar();
final JMenu file = new JMenu("File");
menuBar.add(file);
final JMenuItem fileNew = new JMenuItem("New");
file.add(fileNew);
final JFrame frame1 = new JFrame("First");
frame1.getContentPane().add(new JButton("First"));
frame1.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JFrame frame2 = new JFrame("Second");
frame2.getContentPane().add(new JButton("Second"));
frame2.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
// hot-swap the menu bar to newly activated windows
final WindowListener listener = new WindowAdapter() {
@Override
public void windowActivated(WindowEvent e) {
((JFrame) e.getWindow()).setJMenuBar(menuBar);
}
};
frame1.addWindowListener(listener);
frame2.addWindowListener(listener);
final int offsetX = 200, offsetY = 50;
frame1.pack();
frame1.setLocation(offsetX, offsetY);
frame1.setVisible(true);
frame2.pack();
frame2.setLocation(frame1.getWidth() + offsetX + 10, offsetY);
frame2.setVisible(true);
}
}
使用这种方法,两个框架显示相同的菜单栏,当两个框架都消失时,JVM干净地退出,而不需要显式调用System.exit(int)
。
不幸的是,这种方法并不完美:每次活动窗口发生变化时,菜单栏都会短暂消失。有人知道更好的方法吗?
您可能能够利用JDialog
,它继承了其父的JMenuBar
。要保持对话框非模态,可以使用
-
PropertyChangeEvent
在对话框和主JFrame
之间进行通信,如这里所示。 -
Action
和键绑定在对话框中导航。