I am experiencing a problem with Swing that only occurs when the computer monitor is powered off, but my Swing application continues to run in the background. It seems that whenever the monitor is off, Swing/AWT cancels all painting operations, leading to a number of display issues in the GUI that are visible as soon as the monitor turns back on.
For example, when I turn off the monitor using a custom JNI function and subsequently open a simple message dialog, the message dialog is blank when the monitor turns back on:
But it paints correctly after the next repaint:
Is this the expected behavior of Swing? Is there a way to instruct Swing to continue drawing to the screen even if the monitor is powered off?
EDIT: Here is an SSCCE:
package test;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class App {
public static void main(String[] args) throws Throwable {
System.out.println("***** Please turn off the monitor in the next 70 seconds *****");
Thread.sleep(1000L * 70);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JOptionPane.showMessageDialog(null, "Test");
}
});
}
}
I am using 64-bit Windows 7 Home Premium SP1 and 64-bit Java 1.6.0_24.
EDIT 2: Here is another program with which I experience the effect of "canceled painting operations":
package test;
import static com.mycompany.Util.turnOffMonitors;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class DialogTest extends JDialog {
private final JLabel label;
public DialogTest() {
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
label = new JLabel("Test", JLabel.CENTER);
label.setOpaque(true);
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(BorderLayout.CENTER, label);
this.setPreferredSize(new Dimension(200, 110));
pack();
setLocationRelativeTo(null);
setVisible(true);
Thread t = new Thread() {
#Override
public void run() {
turnOffMonitors();
try {
Thread.sleep(3000L);
} catch (InterruptedException ex) { }
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
label.setBackground(Color.YELLOW);
}
});
}
};
t.start();
}
public static void main(String[] args) throws Throwable {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new DialogTest();
}
});
}
}
Before the monitor shuts off, I see:
With the monitor off, the label background color is changed to yellow in the background. I then move the mouse to turn the monitor back on. The dialog is visually unchanged. It is only after I force a repaint (by ALT-TABbing, for example) do I see the yellow:
EDIT 3: Reported to Oracle as Bug ID 7049597.
I then started the program and stopped
moving the mouse/typing. After one
minute, the screen turned off. I
waited another 20 seconds to move the
mouse. The monitor turned back on and
I saw a blank message dialog.
Using your example, I don't see this on my (non-Windows) platform. You might try the example below, which should alternate between WINDOW_ACTIVATED on wake and WINDOW_DEACTIVATED on sleep. If so, you could extend JDialog and repaint() in windowActivated().
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
/** #see http://stackoverflow.com/questions/6163606 */
public class DialogEventTest extends JDialog {
public DialogEventTest() {
this.setLayout(new GridLayout(0, 1));
this.add(new JLabel("Dialog event test.", JLabel.CENTER));
this.add(new JButton(new AbstractAction("Close") {
#Override
public void actionPerformed(ActionEvent e) {
DialogEventTest.this.setVisible(false);
DialogEventTest.this.dispatchEvent(new WindowEvent(
DialogEventTest.this, WindowEvent.WINDOW_CLOSING));
}
}));
}
private static class WindowHandler extends WindowAdapter {
#Override
public void windowActivated(WindowEvent e) {
System.out.println(e);
}
#Override
public void windowDeactivated(WindowEvent e) {
System.out.println(e);
}
}
private void display() {
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.addWindowListener(new WindowHandler());
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new DialogEventTest().display();
}
});
}
}
The problem probably has more to do with how it repaints when the screen comes on rather than what happens while it's off. You could check by running a screen recorder.
Related
As part of learning SwingWorker on Java swing I created a simple program where a different thread(Other than EDT) is doing some background task and once thats done that thread is updating GUI component (JTextArea).
As I understand if we try to update GUI components from an outside thread ,other than EDT , then UI might get freezed. But this is not happening. I really would like to create that situation(Freeze UI) so as to understand it better. Below is my code that I tried. Can some one help me saying what I need to do on my code so that UI is getting freezed.
App.java
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class App {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainFrame();
}
});
}
}
MainFrame.java
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MainFrame extends JFrame implements ActionListener {
private JButton btn,btn2;
private JTextArea txtArea;
public MainFrame() {
super("Hello World");
setLayout(new BorderLayout());
btn = new JButton("Click Me!");
btn2 = new JButton("Click Me New!");
txtArea = new JTextArea();
btn.addActionListener(this);
add(txtArea,BorderLayout.CENTER);
add(btn,BorderLayout.SOUTH);
add(btn2,BorderLayout.NORTH);
setSize(600,800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
MyTestThread extThr = new MyTestThread();
extThr.setBtnRef(txtArea);
extThr.start();
}
}
MyTestThread.java
import javax.swing.JTextArea;
public class MyTestThread extends Thread {
private int i = 0;
private JTextArea txtAreaRef;
public void setBtnRef(JTextArea ta) {
this.txtAreaRef = ta;
}
public void run() {
while (i < 500000) {
try {
txtAreaRef.append("test"+i+"\n");
i=i+1;
sleep(2000);
} catch (InterruptedException e) {System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
}
I have no idea where you got that information from, but calling GUI updates outside of the EDT do not cause UI freezing.
If you want to produce a freeze, try making an infinite loop inside the EDT, like this.
This will cause the UI to permanently freeze.
public void actionPerformed(ActionEvent e) {
//MyTestThread extThr = new MyTestThread();
//extThr.setBtnRef(txtArea);
//extThr.start();
while(true) {}
}
For some reason my jFrame no longer pops up after I add the menu. Is there something I am missing? I'm trying to make a menu that pops up before the beginning of the game and has buttons "play" as well as a text box that allows the user to input a username.
Any suggestions for how I could fix my code? Thank you!
this is my Menu class:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JPanel;
public class Menu extends JPanel{
private static final long serialVersionUID = 1L;
public Menu() {
JPanel buttonPanel = new JPanel(new GridLayout());
JButton play = new JButton();
JButton help = new JButton();
buttonPanel.add(play);
buttonPanel.add(help);
setFocusable(true);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Game.started = true;
}
});
}
public void paint (Graphics g) {
super.paint(g);
g.setColor(Color.black);
g.fillRect(400, 400, Game.WIDTH, Game.HEIGHT);
}
}
and this is my Main class from which I run my program:
import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Main implements Runnable{
public void run() {
final JFrame frame = new JFrame();
frame.setTitle("Flying Square");
frame.setSize(Game.WIDTH, Game.HEIGHT);
//The menu
final Menu menu = new Menu();
final Game game = new Game();
frame.add(menu, BorderLayout.CENTER);
menu.setVisible(true);
try {while (Game.started == false) {
Thread.sleep(10);
}} catch (InterruptedException e){
e.printStackTrace();
}
frame.remove(menu);
//Main playing area
frame.add(game, BorderLayout.CENTER);
game.setVisible(true);
frame.revalidate();
// Put the frame on the screen
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
// add listeners
frame.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
flyingObject.jump();
}
});
frame.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE)
{
flyingObject.jump();
}
}
});
// Start game
Game.reset();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}
}
So, this...
try {while (Game.started == false) {
Thread.sleep(10);
}} catch (InterruptedException e){
e.printStackTrace();
}
Is blocking the Event Dispatching Thread, preventing it from processing any events and basically causing your program to hang.
This is not how you want to process responses from the user. Your Menu should be monitoring for input from the user, probably through one or more ActionListeners, when an action is triggered, it should be notifying some kind of controller, the controller can then make decisions about what it needs to do, like switch the panels for example
You're going to want to break your code down into at least three chunks, the "game" the "menu" and the "controller", this way it will be easier to manage, rather than trying to retrofit the functionality into an existing code
It would recommend having a look at
How to Use CardLayout to help you facilite the switching of the view
How to Use Key Bindings instead of KeyListener
Model-View-Controller
Observer Pattern
I want to block input to a window, but still be able to move it.
If there was a modal dialog type allowing the window that spawned it to move, then I would be happy.
Say I have a window that opens another window. This second window then opens a modal dialog, which blocks input to the other two windows (fine), but also locks these two windows in place (why - Amigas didn't do this :) ?).
My problem is that I may need to visually read something in the first window for use in the dialog, but this may not be possible because the second window is locked in place, covering it.
I have almost solved this with glass panes, I think. I set the class below to be the glass pane of the root pane of my window, then I call setVisible(true) on it when I want to block and setVisible(false) when I want to unlock the window. When locked, the window greys out to indicate this.
Mouse input is blocked except for closing the window which is fine for now - the problem is that I can still tab around the components on the blocked window and if I get to an editable one, I can edit it with the keyboard, regardless of my empty KeyListener.
Is there an easy way I can prevent the components behind the glass pane from gaining focus?
I am hoping it can be done on the "InputSink" class itself.
I have tried adding its own selfish focus traversal policy and requesting focus when it is visible, but this has no effect.
I have also tried an example I found where a FocusListener was added, whose focusLost method requests focus if the glass pane is visible, but that is overkill, as the window then always stays at front.
Does anybody know a solution in between those two extremes? This is what I have:
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import javax.swing.JPanel;
public class InputSink extends JPanel {
public InputSink() {
this(0.2f); //Default opacity.
}
public InputSink(float alpha) {
setOpaque(false);
setBackground(new Color(0, 0, 0, alpha)); //Just store it here.
addMouseListener(new MouseAdapter() {});
addKeyListener(new KeyAdapter() {});
setFocusTraversalPolicy(new FocusTraversalPolicy() {
#Override
public Component getLastComponent(Container aContainer) {
return InputSink.this;
}
#Override
public Component getFirstComponent(Container aContainer) {
return InputSink.this;
}
#Override
public Component getDefaultComponent(Container aContainer) {
return InputSink.this;
}
#Override
public Component getComponentBefore(Container aContainer, Component aComponent) {
return InputSink.this;
}
#Override
public Component getComponentAfter(Container aContainer, Component aComponent) {
return InputSink.this;
}
});
}
public void paintComponent(final Graphics gfx) { //Handle grey-out.
gfx.setColor(getBackground());
Rectangle rect = gfx.getClipBounds();
gfx.fillRect(rect.x, rect.y, rect.width, rect.height);
}
#Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible)
requestFocus();
}
}
So the version I used following Guillaume Polet's suggestion was
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InputSink extends JPanel {
KeyEventDispatcher blockingDispatcher = new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
return InputSink.this == ((JFrame) SwingUtilities.getWindowAncestor((Component) e.getSource())).getGlassPane(); //Consume!
}
};
public InputSink) {
this(0.2f); //Default opacity.
}
public InputSinkfloat alpha) {
setOpaque(false);
setBackground(new Color(0, 0, 0, alpha)); //Just store it here.
addMouseListener(new MouseAdapter() {});
addKeyListener(new KeyAdapter() {});
}
public void paintComponent(final Graphics gfx) { //Handle grey-out.
gfx.setColor(getBackground());
Rectangle rect = gfx.getClipBounds();
gfx.fillRect(rect.x, rect.y, rect.width, rect.height);
}
#Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible)
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(blockingDispatcher);
else
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(blockingDispatcher);
}
}
Thank you!
You can add a KeyEventDispatcher to the KeyboardFocusManager to block keyboard input.
Small demo below:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestGlassPane {
private static final int COUNTDOWN = 10;
private static final String CLICK_ME = "Click me";
private static final Color GRAY = new Color(192, 192, 192, 128);
private JFrame frame;
private JButton button;
private Timer timer;
private int countdown;
private KeyEventDispatcher blockingDispatcher;
private static class GrayPanel extends JComponent {
#Override
protected void paintComponent(Graphics g) {
g.setColor(GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
public TestGlassPane() {
blockingDispatcher = new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
return true;
}
};
}
protected void initUI() {
frame = new JFrame(TestGlassPane.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button = new JButton(CLICK_ME);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
blockUserInput();
}
});
GrayPanel glassPane = new GrayPanel();
glassPane.addMouseListener(new MouseAdapter() {
});
frame.setGlassPane(glassPane);
frame.add(button);
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
protected void blockUserInput() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(blockingDispatcher);
frame.getGlassPane().setVisible(true);
countdown = COUNTDOWN;
timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
countdown--;
if (countdown == 0) {
timer.stop();
frame.getGlassPane().setVisible(false);
button.setText(CLICK_ME);
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(blockingDispatcher);
} else {
button.setText("We will be back in " + countdown + " seconds");
}
}
});
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestGlassPane().initUI();
}
});
}
}
Normally, the button can be activated with the Space key, but you will see that it actually gets blocked.
because the second window is locked in place
the JDialog has always been 'moveable' for me (using windows).
another possibility to block input:
when you show your non-modal dialog, include this line
frame.setEnabled(false);
also add a windowListener to the dialog, so that on closing
frame.setEnabled(true);
seems to work OK on windows, other platforms unknown
In Swing, there are several ways to capture the event of minimizing a frame (iconifying), but the event happens when the frame is ICONIFIED which means after the frame becomes invisible from the screen.
Now I wish to run some code before disappearance of the frame -- immediately when I click the taskbar button.
In other words, do something when the JFrame is "about to" (NOT AFTER) be minimized. Is it possible to do this?
Use WindowStateListener, and call WindowEvent#getNewState() and check against Frame.ICONIFIED.
Here is an example:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
};
frame.add(panel);
frame.addWindowStateListener(new WindowAdapter() {
#Override
public void windowStateChanged(WindowEvent we) {
if (we.getNewState() == Frame.ICONIFIED) {
System.out.println("Here");
}
}
});
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
Create your own JFrame and override setExtendedState method.
public class MyFrame extends JFrame{
....
setExtendedState(JFrame.ICONIFIED);
....
#Override
public void setExtendedState(int state) {
// your code
super.setExtendedState(state);
};
}
Answer to the question "Is it possible to perform some action BEFORE a JFrame is minimized?"
I would say no unfortunately, I checked the native code for openjdk (windows) for frame and window that sends these events to java-space. And as I understand it, it is a callback from the windows API VM_SIZE message. And the SIZE_MINIMIZED is sent when "The window has been minimized" and is not getting any messages before the actual minimization.
I am using a simple Pomodoro timer but I have no sound/speakers, and so I'd like to signal the end of the timer by simply blanking the screen with a specified color, then toggling it a few times with another color. As if I made my monitor a left-turn car signal. How do I control the monitor like that? Is there some win32 code for this?
I'm familiar with Java, and much less with c# or C++
I'm not sure what is the motive to control the whole screen. Maybe more appropriate would be to show a notification in the system tray.
Anyhow, using Java you could popup a JFrame in full screen mode. Change its background using a timer a couple of times and then close it. Here is a simplified example that demos something similar :
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
class FullScreenFrame extends JFrame{
private static final int MAX_COUNT = 3;
private static final int PERIOD = 1000;
private JPanel panel;
private int count;
public FullScreenFrame(){
super();
setUndecorated(true);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "ESCAPE");
getRootPane().getActionMap().put("ESCAPE", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
panel = new JPanel();
panel.setBackground(Color.GREEN);
add(panel);
new Timer(PERIOD, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (count < MAX_COUNT) {
if (count % 2 == 0){
panel.setBackground(Color.BLACK);
} else {
panel.setBackground(Color.GREEN);
}
count++;
} else {
((Timer) e.getSource()).stop();
dispose();
}
}
}).start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
FullScreenFrame frame = new FullScreenFrame();
frame.setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds());
frame.setAlwaysOnTop(true);
frame.setVisible(true);
}
});
}
}