3

我有一个带有 Swing 用户界面的跨平台 Java 应用程序。在 OS X 上,应用程序使用屏幕菜单栏来获得更原生的用户体验。

通常,应用程序为JFrame每个文档创建一个。屏幕菜单栏必须在所有这些窗口中保持一致。我尝试了几种方法,发现只有一种一致且高效的解决方案,它足够但并不完美。我发布这个问题以防其他人有更好的方法,并希望这些信息对其他人有所帮助。

一些不起作用的方法:

将相同的菜单栏附加到多个窗口

我尝试将相同的内容添加JMenuBar到多个JFrame实例,但Swing 仅支持一次将 JMenuBar 附加到单个 JFrame,即使作为屏幕菜单栏也是如此。

我还使用 AWTMenuBar而不是 进行了测试JMenuBar,但发生了同样的现象。并且MenuBar相比起来有很多限制JMenuBar(例如,没有图标),所以让我们继续我们想要一个JMenuBar.

克隆菜单栏

一种常见的解决方案是JMenuBar为每个新的JFrame. 然而,这至少存在两个问题。首先,您必须保持菜单栏同步。虽然您可以使用侦听器来执行此操作,但仅处理 OS X 平台就需要很多额外的代码。然而,第二个也是更严重的问题是性能:如果您有一个包含数百个菜单项的复杂菜单栏,那么克隆菜单栏会非常慢。我们发现这种方法将新窗口的出现延迟了几秒钟!

使用默认菜单栏

Apple 的 Java 库中添加了一种新方法,用于OS X v10.6 Update 1 和 10.5 Update 6:.Application.setDefaultMenuBar(JMenuBar)

此方法的既定目的是在 no 处于活动状态时提供菜单栏,但是当没有自己的 noJFrame处于活动状态时,它也会显示默认菜单栏。JFrameJMenuBar

但是,该功能存在几个主要问题setDefaultMenuBar

  1. 加速器不起作用。我通过自己处理所有按键来避免我们的应用程序中的这个问题,但这仍然是不幸的。
  2. 截至 2012 年 12 月,setDefaultMenuBar 在 Java7 上仍然不可用。我们显然希望避免使用已弃用或不受支持的 API。
  3. 最关键的是,调用setDefaultMenuBar 会阻止 JVM 正常关闭。即使是随后的调用setDefaultMenuBar(null)也不会释放必要的资源。

简而言之,setDefaultMenuBar这似乎根本不是一种安全可靠的方法。

所以,问题是:实现一致屏幕的最可靠、高性能和兼容(跨 OS X 版本)的方法是JMenuBar什么?

4

2 回答 2

3

我发现效果很好的解决方案是通过向应用程序的每个窗口windowActivated添加 a 来监听事件。WindowListener然后,将新激活的窗口设置为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).

不幸的是,这种方法并不完美:每次活动窗口更改时,菜单栏都会短暂消失。有人知道更好的方法吗?

于 2013-04-23T17:48:52.403 回答
2

您也许可以利用JDialog继承其父级的JMenuBar. 要保持对话框无模式,您可以使用

  • PropertyChangeEvent按照这里JFrame的建议,在对话框和主要之间进行通信。

  • Action键绑定在对话框之间导航。

于 2013-04-24T16:15:59.217 回答