Java 使OSX屏幕JMenuBar在windows上一致工作的最佳方法是什么?

Java 使OSX屏幕JMenuBar在windows上一致工作的最佳方法是什么?,java,macos,swing,menubar,jmenubar,Java,Macos,Swing,Menubar,Jmenubar,我有一个带有Swing用户界面的跨平台Java应用程序。在OSX上,应用程序提供了更为本地的用户体验 通常,应用程序会为每个文档创建一个JFrame。屏幕菜单栏必须在所有这些窗口中保持一致。我尝试了几种方法,但只找到了一种一致且性能良好的解决方案,这是足够的,但并不完美。我发布这个问题,以防其他人有更好的方法,并希望这些信息能帮助其他人 有些方法不起作用: 将同一菜单栏附加到多个窗口 我尝试将相同的JMenuBar添加到多个JFrame实例中,但是,甚至作为屏幕菜单栏 我还使用AWTMenuBa

我有一个带有Swing用户界面的跨平台Java应用程序。在OSX上,应用程序提供了更为本地的用户体验

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

有些方法不起作用:

将同一菜单栏附加到多个窗口 我尝试将相同的
JMenuBar
添加到多个
JFrame
实例中,但是,甚至作为屏幕菜单栏

我还使用AWT
MenuBar
而不是
JMenuBar
进行了测试,但也出现了同样的现象。与
JMenuBar
相比,
MenuBar
有许多限制(例如,没有图标),因此让我们继续要求使用
JMenuBar

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

使用默认菜单栏 苹果的Java库中添加了一个新方法:
Application.setDefaultMenuBar(JMenuBar)

此方法的规定目的是在无
JFrame
处于活动状态时提供一个菜单栏,但当自身无
JMenuBar
JFrame
处于活动状态时,也会显示默认菜单栏

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

  • 。我在我们的应用程序中避免了这个问题,但这仍然是不幸的
  • 截至2012年12月。我们显然希望避免使用不推荐或不受支持的API
  • 最关键的是,调用
    setDefaultMenuBar
    。即使随后调用
    setDefaultMenuBar(null)
    也无法释放必要的资源
  • 简而言之,
    setDefaultMenuBar
    似乎根本不是一种安全可靠的方法


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

    我发现效果最好的解决方案是通过在应用程序的每个窗口中添加
    WindowListener
    来监听
    windowActivated
    事件。然后,将新激活的窗口的
    JMenuBar
    设置为我们想要显示的唯一菜单栏

    以下是一份:

    使用这种方法,两个帧都显示相同的菜单栏,当两个帧都消失时,JVM将干净地退出,而无需显式调用
    System.exit(int)


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

    您可以利用继承其父级的
    JMenuBar
    JDialog
    。要使对话框保持非模态,可以使用

    • propertychangevent
      在对话框和主
      JFrame
      之间进行通信,如建议的那样

    • 并在对话框之间导航


    虽然您的解决方案很有效,但我个人认为它不令人满意。最好的解决方案是为菜单进行MVC设计。创建一个“菜单模型”,集中你的菜单层次、你的动作、你的动作和一切。创建显示该模型的正确视图。然后,为每个帧创建不同的视图,但基于相同的模型。当模型更改时,视图将到处更改。也请阅读。@GuillaumePolet:谢谢你的建议。实际上,我的应用程序正是这样做的:。事实上,它相当痴迷于MVC。不幸的是,最终仍然必须创建一个
    JMenuBar
    ,并将其附加到每个Swing窗口。你能详细说明一下MVC是如何避开这个问题的吗?@GuillaumePolet:至于使用multiple
    JFrame
    s是一种不好的做法,这个链接的上下文对于它回答的问题非常具体,我认为在这里不适用。无论如何,为了兼容性和可用性,我们的应用程序是按照以前的版本建模的,带有多窗口(即SDI)设计,我们必须保留这个设计。(不过,我们也有MDI实现。)在更广泛的层面上,我不同意使用多个窗口是“糟糕的做法”;就连OSX本身也附带了许多多窗口应用程序:Safari、Terminal、TextEdit、Preview等。@trashgood:正如我所说,我们还有一个MDI UI,它使用
    JInternalFrame
    。但我们仍然需要一个具有多个窗口的SDI版本。但是使用非模态对话框是一个非常有趣的建议;我没有意识到一个
    JDialog
    继承了其父
    JMenuBar
    。不幸的是,这种方法导致所有子对话框在Windows上都缺少任务栏条目,因此,例如,您不能再在窗口之间使用Alt+Tab,这可能不是一个可接受的折衷办法。@ctrueden:interest;在OSX上,命令选项卡在应用程序之间切换,但不在多个帧之间切换,例如;我使用
    操作
    &键绑定在无模式对话框之间切换。抱歉,我之前忽略了这一点。谢谢,我更喜欢这个解决方案,而不是
    windowActivated
    方法,因为菜单栏保持一致,切换活动窗口时没有闪烁或延迟。这种方法应该在acr中广泛应用
    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);
      }
    
    }