最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

fullscreen - How to implement Full Screen mode for a Windowed Java program - Stack Overflow

programmeradmin3浏览0评论

Expectation: It'll be easy to implement a Full Screen mode for my Java app (a fork of an existing app, for which the code is extensive and can be very difficult to work with). All I need to do is remove the border and menu bar, set the frame to the size of the screen, ensure it's "always on top". A few lines of code, 1 hour tops, little bit of testing.

Step 1: Removing the menu bar.

This was relatively easy, it's now a separate option with which users can choose to show/hide the menu bar if they wish. Done.

Step 2: Removing the border.

And this is as far as I can get. Set up the Full Screen option and call this method after exiting the config menu (making use of setUndecorated(true)):

void toggleFullScreen() {
    boolean shouldDisplayFullScreen = GameController.isOptionEnabled(GameController.Options.FULL_SCREEN);
    GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

if (shouldDisplayFullScreen) {
    Core.saveProgramProps(); // Store current window size and position
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    setExtendedState(JFrame.NORMAL); // Reset state before changing size
    setUndecorated(true)            
    setJMenuBar(null); // Hide the menu bar            
    setSize(screenSize.width, screenSize.height);
    setLocation(0, 0);
    setAlwaysOnTop(true);
    setResizable(false);
} else {
    // Restore windowed mode - TODO: This currently fires every time the Options menu is opened and closed; we need to see if fullscreen has actually changed
    setAlwaysOnTop(false);
    applyPropsWindowSize();
    applyPropsWindowPosition();
    toggleMenuBarVisibility(); // Restore the menu bar depending on props
    setResizable(true);
    setUndecorated(false);
}

validate();

}

We get this error:

    public void setUndecorated(boolean undecorated) {
    /* Make sure we don't run in the middle of peer creation.*/
    synchronized (getTreeLock()) {
        if (isDisplayable()) {
            throw new IllegalComponentStateException("The frame is displayable.");

Erm...OK? Well, surely the frame needs to be displayable in order to... be displayed! Apparently not, so I get to Google searching and eventually find this thread. With a bit of manoeuvering, I add the example code to the main class, to be run as soon as the app starts and as default behaviour (purely for testing purposes at this point, eventually we want full control over the Full Screen mode):

public MainFrame() {
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    device = env.getDefaultScreenDevice();
    isFullScreen = device.isFullScreenSupported();

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setUndecorated(isFullScreen);
    setResizable(false);
    
    if (isFullScreen) {
        device.setFullScreenWindow(this);
    } else {
        // Windowed mode behavior
        setExtendedState(JFrame.MAXIMIZED_BOTH);
    }

    // Apply other initializations after fullscreen/window setup
    try {
        String currentFolderStr = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().getFile(), "UTF-8");
        System.out.println("Current directory: " + currentFolderStr);
        boolean successful = Core.init(currentFolderStr); // initialize Core object
        if (!successful) {
            System.exit(0);
        }
    } catch (LException ex) {
        JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
        System.exit(1);
    } catch (Throwable ex) {
        ToolBox.showException(ex);
        System.exit(1);
    }

    initComponents();
    
    if (!isFullScreen)
        setMinimumSize(getSize());
    
    RepeatingReleasedEventsFixer.install();
    
    setVisible(true);
}

And, we're finally getting somewhere. The window can now be displayed undecorated. But, I get a ton of errors (not worth printing them all here), the window doesn't span the width of the screen, and disappears behind other active windows whenever a sub-window is opened. Not ideal, but at least we know the app can support being borderless, etc.

However, the only reason this worked at all is because it's all getting set up before the window is displayed. Once it's displayed, the original "toggle" method still generates the same "frame is displayable" error when we attempt to switch between the two modes.

Obviously, this would all need to be refactored into a single method and be callable whether or not the window is displayed, as the point is to be able to switch between FullScreen and Windowed whilst the app is open.

Any clues?

Expectation: It'll be easy to implement a Full Screen mode for my Java app (a fork of an existing app, for which the code is extensive and can be very difficult to work with). All I need to do is remove the border and menu bar, set the frame to the size of the screen, ensure it's "always on top". A few lines of code, 1 hour tops, little bit of testing.

Step 1: Removing the menu bar.

This was relatively easy, it's now a separate option with which users can choose to show/hide the menu bar if they wish. Done.

Step 2: Removing the border.

And this is as far as I can get. Set up the Full Screen option and call this method after exiting the config menu (making use of setUndecorated(true)):

void toggleFullScreen() {
    boolean shouldDisplayFullScreen = GameController.isOptionEnabled(GameController.Options.FULL_SCREEN);
    GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

if (shouldDisplayFullScreen) {
    Core.saveProgramProps(); // Store current window size and position
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    setExtendedState(JFrame.NORMAL); // Reset state before changing size
    setUndecorated(true)            
    setJMenuBar(null); // Hide the menu bar            
    setSize(screenSize.width, screenSize.height);
    setLocation(0, 0);
    setAlwaysOnTop(true);
    setResizable(false);
} else {
    // Restore windowed mode - TODO: This currently fires every time the Options menu is opened and closed; we need to see if fullscreen has actually changed
    setAlwaysOnTop(false);
    applyPropsWindowSize();
    applyPropsWindowPosition();
    toggleMenuBarVisibility(); // Restore the menu bar depending on props
    setResizable(true);
    setUndecorated(false);
}

validate();

}

We get this error:

    public void setUndecorated(boolean undecorated) {
    /* Make sure we don't run in the middle of peer creation.*/
    synchronized (getTreeLock()) {
        if (isDisplayable()) {
            throw new IllegalComponentStateException("The frame is displayable.");

Erm...OK? Well, surely the frame needs to be displayable in order to... be displayed! Apparently not, so I get to Google searching and eventually find this thread. With a bit of manoeuvering, I add the example code to the main class, to be run as soon as the app starts and as default behaviour (purely for testing purposes at this point, eventually we want full control over the Full Screen mode):

public MainFrame() {
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    device = env.getDefaultScreenDevice();
    isFullScreen = device.isFullScreenSupported();

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setUndecorated(isFullScreen);
    setResizable(false);
    
    if (isFullScreen) {
        device.setFullScreenWindow(this);
    } else {
        // Windowed mode behavior
        setExtendedState(JFrame.MAXIMIZED_BOTH);
    }

    // Apply other initializations after fullscreen/window setup
    try {
        String currentFolderStr = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().getFile(), "UTF-8");
        System.out.println("Current directory: " + currentFolderStr);
        boolean successful = Core.init(currentFolderStr); // initialize Core object
        if (!successful) {
            System.exit(0);
        }
    } catch (LException ex) {
        JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
        System.exit(1);
    } catch (Throwable ex) {
        ToolBox.showException(ex);
        System.exit(1);
    }

    initComponents();
    
    if (!isFullScreen)
        setMinimumSize(getSize());
    
    RepeatingReleasedEventsFixer.install();
    
    setVisible(true);
}

And, we're finally getting somewhere. The window can now be displayed undecorated. But, I get a ton of errors (not worth printing them all here), the window doesn't span the width of the screen, and disappears behind other active windows whenever a sub-window is opened. Not ideal, but at least we know the app can support being borderless, etc.

However, the only reason this worked at all is because it's all getting set up before the window is displayed. Once it's displayed, the original "toggle" method still generates the same "frame is displayable" error when we attempt to switch between the two modes.

Obviously, this would all need to be refactored into a single method and be callable whether or not the window is displayed, as the point is to be able to switch between FullScreen and Windowed whilst the app is open.

Any clues?

Share Improve this question asked Feb 16 at 22:33 WilliciousWillicious 734 bronze badges 9
  • 1 Step 1: read the documentation. Step 2: Remove Toolkit.getDefaultToolkit().getScreenSize();. The only correct ways to make your window fill the screen are setExtendedState(Frame.MAXIMIZED_BOTH) and setFullScreenWindow. – VGR Commented Feb 16 at 23:34
  • 1 @VGR According to the documentation, "A frame may have its native decorations (i.e. Frame and Titlebar) turned off with setUndecorated. This can only be done while the frame is not displayable." This is something I'm already aware of, and is in fact the very problem I'm having. If we can't use setUndecorated when the frame is displayable, how can we turn FullScreen on/off on the fly with the window visible and displayed? Is it necessary to close and re-open the program? Surely not! Then, what's the workaround? Thanks for the Step 2 tips – Willicious Commented Feb 17 at 0:52
  • @VGR Reading further, "A component is made undisplayable either when it is removed from a displayable containment hierarchy or when its containment hierarchy is made undisplayable. A containment hierarchy is made undisplayable when its ancestor window is disposed." I'll try this tomorrow; maybe there's a way to temporarily remove the frame from the hierarchy without outright closing it (and without even having to rebuild it, ideally). – Willicious Commented Feb 17 at 0:58
  • 2 While it might be possible to transition a Frame between fullscreen and windowed, I've always found it easier to just have two frames, only one of which is visible at any given time: one for windowed mode, and one for fullscreen. To switch between the modes, I move the component representing the window content into the desired Frame. – VGR Commented Feb 17 at 2:16
  • 1 Hello Willicious, as a precarious solution, it occurs to me that you could create two frames, one decorated in window, and the other not decorated in full screen, and switch between them, adding and removing the panel and making it visible or not, as corresponds. – Marce Puente Commented Feb 17 at 2:42
 |  Show 4 more comments

1 Answer 1

Reset to default 0

I find it easier to make two JFrames: one for normal windowed mode, and one for fullscreen mode. I then add my window contents to the contentPane of one JFrame when switching modes. Since a component can have only one parent at any given time, adding it to a different JFrame automatically removes it from the other JFrame.

In this demo, pressing ‘F’ switches between fullscreen and windowed modes:

import java.time.LocalTime;

import java.io.Serial;

import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Font;
import java.awt.Component;
import java.awt.Window;

import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.AbstractAction;
import javax.swing.Timer;

public class FullScreenDemo {
    private final JFrame normalWindow;

    private final JFrame fullScreenWindow;

    private final JComponent contents;

    public FullScreenDemo() {
        contents = new JPanel() {
            @Serial
            private static final long serialVersionUID = 1;

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(800, 600);
            }

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);

                String s = String.format("%tT", LocalTime.now());
                Font font = g.getFont();
                font = font.deriveFont(getHeight() / 4f);
                g.setFont(font);

                int width = (int) Math.ceil(
                    g.getFontMetrics().getStringBounds(s, g).getWidth());

                int x = (getWidth() - width) / 2;
                int y = getHeight() / 2;

                g.drawString(s, x, y);
            }
        };

        contents.setFocusable(true);

        String fullscreenID = "toggle-fullscreen";
        contents.getActionMap().put(fullscreenID,
            new AbstractAction() {
                @Serial
                private static final long serialVersionUID = 1;

                @Override
                public void actionPerformed(ActionEvent event) {
                    toggleFullScreen();
                }
            });
        contents.getInputMap().put(KeyStroke.getKeyStroke("F"), fullscreenID);

        normalWindow = new JFrame("Full-Screen Demo");
        fullScreenWindow = new JFrame("Full-Screen Demo");
        fullScreenWindow.setUndecorated(true);

        WindowAdapter closeListener = new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                quit();
            }
        };

        normalWindow.addWindowListener(closeListener);
        fullScreenWindow.addWindowListener(closeListener);

        normalWindow.getContentPane().add(contents);
        normalWindow.pack();
        normalWindow.setLocationByPlatform(true);

        Timer timer = new Timer(1000, e -> contents.repaint());
        timer.start();
    }

    public void show() {
        normalWindow.setVisible(true);
    }

    private void quit() {
        System.exit(0);
    }

    private GraphicsDevice screenOf(Window window) {
        GraphicsConfiguration config = window.getGraphicsConfiguration();
        GraphicsDevice screen;
        if (config != null) {
            return config.getDevice();
        } else {
            GraphicsEnvironment env =
                GraphicsEnvironment.getLocalGraphicsEnvironment();
            return env.getDefaultScreenDevice();
        }
    }

    private void toggleFullScreen() {
        if (normalWindow.isAncestorOf(contents)) {
            GraphicsDevice screen = screenOf(normalWindow);

            normalWindow.setVisible(false);

            fullScreenWindow.getContentPane().add(contents);
            fullScreenWindow.pack();
            screen.setFullScreenWindow(fullScreenWindow);
        } else {
            GraphicsDevice screen = screenOf(fullScreenWindow);

            screen.setFullScreenWindow(null);
            fullScreenWindow.setVisible(false);

            normalWindow.getContentPane().add(contents);
            normalWindow.pack();
            normalWindow.setVisible(true);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            FullScreenDemo demo = new FullScreenDemo();
            demo.show();
        });
    }
}
发布评论

评论列表(0)

  1. 暂无评论