Creating an offscreen frame in Java (or: how to av

2019-01-26 12:09发布

问题:

I am making a Mac application, and I want my menu bar to look right.

Any Mac user knows the menu bar should be in the top screen menu. Setting apple.laf.useScreenMenuBar to true in the property list file gets rid of the in-frame menu bars and moves the menu bar of the current focused window to the screen menu.

However, when all windows are hidden or when there are no windows, there are no menu bars to move to the top, and you just get a blank menu. I heard a solution to this was to create an offscreen window that is focused when no others are. The only purpose of it would be its menu, so that it could fill in when the others are gone.

However, I've been getting loads of problems. I can't seem to move the window off the screen because Macs won't let you set the coordinates to something past the size of the screen; it just cuts it off and positions it at the edge instead. Is there something else I have to do to make an offscreen window?

回答1:

You should definitely consider WizardOfOdds' very helpful answer. Using "The Application Menu" correctly will help, and it's easy to set up a minimal Info.plist to get started. A persistent File menu will allow your application to open a new window when others are closed. This answer links to a simple example.

Although Apple's Human Interface Guidelines are an excellent guide to what your users will expect, you can certainly experiment with the approach you suggested in your question. In particular, you might try setLocation(Short.MIN_VALUE, Short.MIN_VALUE) on the invisible window. In addition, you might want to respond to a WindowEvent in some special way if it signals the close of the last visible window.

Addendum: When your listener sees the last visible window close, create a new, empty application window. Alternatively, move the invisible window onscreen and make it visible until the user decides how to proceed.

Addendum: Mac OS X helpfully prevents a visible window form being moved offscreen, but it's easy to put an invisible window in limbo, as shown below.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;

public class FrameTest extends JFrame {

    private static FrameTest marco;
    private static FrameTest polo;

    private static class MyPanel extends JPanel {

        public MyPanel() {
            super(true);
            final JToggleButton b = new JToggleButton("Test");
            b.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    if (b.isSelected()) {
                        polo.setLocation(100, 100);
                        polo.setVisible(true);
                    }
                    else {
                        polo.setVisible(false);
                        polo.setLocation(Short.MIN_VALUE, Short.MIN_VALUE);
                    }
                }
            });
            this.add(b);
        }
    }

    public FrameTest(String title) {
        super(title);
        this.setLayout(new BorderLayout());
        this.add(new MyPanel());
        this.pack();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(final String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                marco = new FrameTest("Marco");
                marco.setLocationRelativeTo(null);
                marco.setVisible(true);
                polo = new FrameTest("Polo");
                polo.setLocation(Short.MIN_VALUE, Short.MIN_VALUE);
            }
        });
    }
}


回答2:

I know that this post is quite old, anyway, I had the same problem and found the solution. Actually it's quite simple. Just don't add the JMenuBar to your main frame when running on mac os x, but to your application using

com.apple.eawt.Application.getApplication().setDefaultMenuBar(menuBar);

Now the MenuBar is still displayed even if you set all frame's visibility to false.



回答3:

Not a direct solution, but I think some create a 1-pixel window instead. That yields complaints though, like one described at Super User: Chaotic behavior of a dead pixel on my iMac 24"...



回答4:

First a note: your question seems really to be "How to have a Window menu following the Apple Human Interface Guidelines" and not "creating an offscreen frame in Java", which seems like a monstrous hack.

I suggest checking Apple's "Mac OS X Integration for Java", which, under "Window menu", shows apparently exactly what you're trying to achieve:

Apple Human Interface Guidelines suggests that all Mac OS X applications should provide a Window menu to keep track of all currently open windows. A Window menu should contain a list of windows, with a checkmark next to the active window.



回答5:

This is a comment to trashgod's answer. It's too big so I have to move it here >.<


More problems!

I'm getting 2 errors:

line 23: The method itemStateChanged(ItemEvent) of type new ItemListener(){} must override a superclass method

line 50:The method run() of type new Runnable(){} must override a superclass method

Any help? I've never encountered this before. I don't know what it means.

EDIT: I have more problems! I need the offscreen window to be visible while it is offscreen for it to produce a menu to move to the screen menu bar. When I use the code

offScreen = new JFrame("WTF?!  You can see me?");
offScreen.setSize(400,300);
offScreen.setLocation(0, java.awt.Toolkit.getDefaultToolkit().getScreenSize().height+50);
System.out.println(offScreen.getLocation());
offScreen.setVisible(true);
System.out.println(offScreen.getLocation());

I get the output:

java.awt.Point[x=0,y=1100]
java.awt.Point[x=0,y=961]

It moves it back once it's made visible again!

I've searched for ages and I can't find anything.



回答6:

This code works in Java 7:

if( isMac ) {
    //This creates an invisible frame so that we always have a menu bar visible
    JFrame menuFrame = new JFrame();
    menuFrame.setUndecorated( true );
    menuFrame.setJMenuBar( defaultMenuBar );
    AWTUtilities.setWindowOpaque( menuFrame, false );
    menuFrame.setBounds( 0,0,1,1 );
    menuFrame.setVisible( true );
}

Just call this before you open any other windows, and it will stay in the background and automatically become the focused window when others are closed. You can still use the com.apple.eawt.Application.getApplication().setDefaultMenuBar(menuBar) method in your application so that you don't need to call setJMenuBar() on each JFrame.