Monday, August 20, 2007

Toggle Fullscreen Mode

Purpose

To show how you can switch a window between being windowed and fullscreen with the press of a button.


Diving Right In

Ok, so in a previous post I showed how you can start a JFrame in fullscreen mode by removing decorations on the frame and also by changing the screens display mode. Well now I am going to use that code modify it so that you can toggle or switch between windowed and fullscreen modes just by pressing a single key.


Windowed Mode

The following code will change a fullscreened JFrame back to windowed mode.

//set the display mode back to the what it was when
//the program was launched.
device.setDisplayMode(dispModeOld);

//hide the frame so we can change it.
setVisible(false);

//remove the frame from being displayable.
dispose();

//put the borders back on the frame.
setUndecorated(false);

//needed to unset this window as the fullscreen window.
device.setFullScreenWindow(null);

//make sure the size of the window is correct.
setSize(800,600);

//recenter window
setLocationRelativeTo(null);

//reset the display mode to what it was before
//we changed it.
setVisible(true);
There are a couple of key function calls here, one is the dispose call since it allows us to add the decorations back onto the window. Without it right before the call to setUndecorated we would get a java.awt.IllegalComponentStateException with the message "The frame is displayable". We could however remove the call to setUndecorated but then there would be no Border (i.e no title bar, minimize, maximize, or exit buttons) around the window when in windowed mode.
Another nice call that you could remove without any problems is the setLocationRelativeTo(null); this call centers the window on screen. You need to make sure you call this only after the size has been reset since it won't be centered if you do that.

One of the most important calls is the first one displayModeOld is a member variable that we must set in the JFrame's constructor and anytime before we switch to a custom screen mode. Idea: What would be a good idea is to check to see if the current screen mode is what we want and if not then save the old one and change to one we want.

Fullscreen Mode

Ok, so now that we can switch back to windowed here again is the code for changing to fullscreen mode there are a couple of things that needed to be added since we need to be doing this while the window is already being displayed.
//save off the old display mode.
dispModeOld = device.getDisplayMode();

//hide everything
setVisible(false);

//remove the frame from being displayable.
dispose();

//remove borders around the frame
setUndecorated(true);

//make the window fullscreen.
device.setFullScreenWindow(this);

//attempt to change the screen resolution.
device.setDisplayMode(dispMode);

//show the frame
setVisible(true);

Notice that we first save the current screen mode so the next time the window is changed we can reset the screen mode back to what it was before we changed it.


Encapsulation

Alright, now we need to put it all in one nice easily managable function.

/**
 * Method allows changing whether this
 * window is displayed in fullscreen or
 * windowed mode.
 *
 * @param fullscreen true = change to fullscreen,
 *                   false = change to windowed
 */
public void setFullscreen(boolean fullscreen) {
   //are we actually changing modes.
   if( this.fullscreen != fullscreen ) {

      //change modes.
      this.fullscreen = fullscreen;

      // toggle fullscreen mode
      if(!fullscreen) { 

         //change to windowed mode.

         //...windowed mode code.

      }else{
         //change to fullscreen.

         //...fullscreen mode code.

      } // end if

      //make sure that the screen is refreshed.
      repaint();
   }
}


We only want to actually change display modes if we are switching from fullscreen to windowed or vice versa, since trying to set some of the properties will cause exceptions to be thrown.


Seeing Is Believing

12 comments:

Leo said...

Very nice piece of code. Helped me. Thanks!

Nick D said...

Thanks for the feedback! I'm glad I could be of assistance. I've seen this code also used in an open source project and they added support for multiple screen devices. Something that is worth checking out. The modified code they use the projects main website Alchemy

Dolda2000 said...

Thanks for the post. Before I read it, I thought that dispose destroyed a window forever, so I though I'd have to create a new window and rebuild its contents to switch between windowed and fullscreen modes.

However, it is worth mentioning that it seems that mode switching should be done on the AWT event dispatch thread. I was running my own event dispatching on another thread, and when I tried switching modes there, I'd get deadlocks left and right. I just thought I'd add that if someone else gets in trouble.

sironmon said...

Thanks, I looked all over for how to do this and was really frustrated. This really helped.

Anonymous said...

thanks

j@g said...

Thanks! It was quite helpful! How would you implement a smooth transition between display modes as in any pro app?

MorbidMorvick said...

j@g: not sure what type of smooth transitioning you're looking to achieve. Let me know of the "pro-app" you are thinking of and I can try to create a similar effect.

Alternative: When switching to fullscreen you can ignore "i.e. not perform" the display mode change and instead just re-scale the drawn images to the current size of the display and create the fullscreen undecorated window, this will give you a bit of "smoothness" since I've noticed whenever you "actually" change display modes the monitor usually blinks and other such unwanted behavior happens like resizing and moving multiple windows between displays in a multiple display setup etc.

Note: In java, opengl or directx when you change display modes it's a provided functionality of the video driver so the "smoothness" would depend both on the display (i.e. monitor, tv), video card drivers and OS, not really something you can control to the greatest degree. In AAA games made with directx there is usually always a lag when changing display modes because directx can send a device lost call and you need to reset the device and reload resources which takes time, same with alt+tab events.

Let me know if that sounds correct, and if the alternative method is what you're looking for, I would love to see an app that does display changes smoothly but as far as I know people expect to have to wait when changing display modes or switching from fullscreen to windowed modes, or alt+tabbing at least in Windows environments.

Tanshaydar said...

Hi;
I'm making a little game for a term project, but I'm stuck at where I needed to implement a toggle fullscreen feature.
My JFrame is double buffered and whenever I try to set full screen, it just throws nullpointerexception on device.setFullScreenWindow(this); line, as 'this' is the JFrame itself.

MorbidMorvick said...

The 'device' variable is null for some reason, so that is whats throwing the NullPointerException. In the example file the main method gets a valid device using GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice() and passes it to the constructor.

If you have the getDefaultScreenDevice and it's returning null you may not have permissions on the machine your using to force fullscreen or change display modes. So if you do happen to have the call to get the device from the graphicsenvironment then I think that is why it's returning null.

Though I could have sworn that even if you don't have permissions to change the device you would still be able to get the default device, since the other alternative at that point is to resize the window to fit the screen size with an undecorated JFrame calls you will need for that are GraphicsConfiguration.getBounds() (screen bounds note multiscreen displays can give negative values for x,y as noted in javadocs), JFrame.setUndecorated(true) this must be set before the call to show the JFrame. There also is a call in GraphicsEnvironment.getMaximumWindowBounds() which gives you the space used when you maximize windows so minus toolbars and menubars the host desktop shell is using.

I have seen oddities with some xwindowing clients in single window modes instead of remote desktop where setUndecorated has odd effects. Good luck let me know more details if you can't get it working.

Tanshaydar said...

I got it working with a little workaround. I overridden the addNotify method of native JFrame component to set it doublebuffered like this:

@Override
public void addNotify() {
super.addNotify();
// Buffer
createBufferStrategy(2);
strategy = getBufferStrategy();
}
I also created a new variable that holds the old display mode and it helped a little more. I could share the code I wrote if you want. My project is almost finished and works good. Thanks to your tutorials :)

MorbidMorvick said...

Wow very pleased to hear that Tanshaydar it's always good to know someone has found a use for these snippets.

I am definitely interested in seeing the code. I'd like to also see the broken code. I must admit I am a little curious as to why you got the NullPointerException. The code might also help others, it's up to you if you wanna post a link to it, but I would encourage some sharing.

Tanshaydar said...

Hi;
Sorry for the late reply.
Here's the code:
http://pastebin.com/RNFE2P0E

Only noticeable change is that I overridden addNotify method of JFrame.

This is my screenmanager of the term project, a Pang imitation game written in Java.

Post a Comment