什么是使OS X屏幕JMenuBar跨窗口一致工作的最佳方法?



我有一个带有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功能有几个主要问题:

  1. 加速器不工作。在我们的应用程序中,我通过自己处理所有的按键来避免这个问题,但它仍然是不幸的。
  2. 截至2012年12月,setDefaultMenuBar在Java7上仍然不可用。我们显然希望避免使用不支持的api。
  3. 最关键的是,调用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键绑定在对话框中导航。

相关内容

  • 没有找到相关文章