I am just wondering... How to block main thread as JOptionPane (its confirmation dialog) does to be able hold execution until it returns some value? I've never done it before so I am interested to find an optimal way...
to be more clear I am interested to express the conception as something like this but I am not sure how to make it wait until an event happens in popupmenu for example:
...
int a;
int x;
void showPopup()
{
a=MyPopupMenu.aPopupMenu();
if(a==0){System.out.println("YES");}
if(a==1){System.out.println("NO"); return;}
x++;
}
Thanks
Use wait/notify. The main thread will do:
synchronized(object) {
object.wait(timeOut);
}
and the handler (ie listener) for whatever GUI you are using (a popup in your example) will do:
synchronized(object) {
object.notify();
}
Here is a stripped-down instructional example using wait/notify
public class ModalPopUp {
JPopupMenu popUpMenu;
JMenuItem menuItem;
static Object modalMonitor = new Object();
public void popIt(Component parent, int x, int y) {
popUpMenu.show(parent, x, y);
}
public void stopIt() {
popUpMenu.setVisible(false);
}
public ModalPopUp() {
popUpMenu = new JPopupMenu();
menuItem = new JMenuItem("Click me to Continue");
popUpMenu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
synchronized (modalMonitor) {
modalMonitor.notify();
}
}
});
}
public static void main(String args[]) throws Exception {
ModalPopUp p = new ModalPopUp();
JFrame frame = new JFrame();
frame.setSize(400, 400);
frame.setVisible(true);
p.popIt(null, 100, 100);
System.out.println("Waiting upto 42s for popup click");
synchronized (modalMonitor) {
modalMonitor.wait(42000);
}
p.stopIt();
System.out.println("Popup was clicked or 42s passed");
}
}
Instead of using the static JOptionPane methods you can use an instance of a JDialog directly, allowing you to customize its behavior, such as the modality.
How to Make Dialogs
Related
Is it possible to be notified whenever any window in the application was created or closed?
At the moment I'm polling Window.getWindows() but I would prefer to get notified instead.
What I have:
List<Window> previousWindows = new ArrayList<>();
while (true) {
List<Window> currentWindows = Arrays.asList(Window.getWindows());
for (Window window : currentWindows) {
if (!previousWindows.contains(window)) {
//window was created
}
}
for (Window window : previousWindows) {
if (!currentWindows.contains(window)) {
//window was closed
}
}
previousWindows = currentWindows;
Thread.sleep(1000);
}
What I'd like:
jvm.addWindowListener(this);
#Override
public void windowWasDisplayed(Window w) {
//window was created
}
#Override
public void windowWasClosed(Window w) {
//window was closed
}
You can register listeners that receive any subset of types of AWT events via the windowing Toolkit. From those you can select and handle the WindowEvents for windows being opened and closed, something like this:
class WindowMonitor implements AWTEventListener {
public void eventDispatched(AWTEvent event) {
switch (event.getID()){
case WindowEvent.WINDOW_OPENED:
doSomething();
break;
case WindowEvent.WINDOW_CLOSED:
doSomethingElse();
break;
}
}
// ...
}
class MyClass {
// alternative 1
public void registerListener() {
Toolkit.getDefaultToolkit().addAWTEventListener(new WindowMonitor(),
AWTEvent.WINDOW_EVENT_MASK);
}
// alternative 2
public void registerListener(Component component) {
component.getToolkit().addAWTEventListener(new WindowMonitor(),
AWTEvent.WINDOW_EVENT_MASK);
}
}
I would recommend alternative 2, where the Component from which you obtain the Toolkit is the main frame of your application (there should be only one), but alternative 1 should work out for you if you have to do this without reference to any particular component (for instance, before any have been created).
Do note, however, that registering an AWTEventListener is subject to a security check.
If you create the additional windows (I assume JFrames) yourself, you can use the addWindowListener method. The WindowAdapter abstract class allows you to override methods for the events you are interested in:
import java.awt.event.*;
import javax.swing.*;
public class MultipleWindows {
public static void main(String[] arguments) {
SwingUtilities.invokeLater(() -> new MultipleWindows().createAndShowGui());
}
private void createAndShowGui() {
JFrame frame = new JFrame("Stack Overflow");
frame.setBounds(100, 100, 800, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.add(new JLabel("Testing multiple windows..."));
frame.getContentPane().add(panel);
WindowAdapter windowAdapter = new WindowAdapter() {
#Override
public void windowOpened(WindowEvent windowEvent) {
System.out.println("Window opened: "
+ windowEvent.getWindow().getName());
}
#Override
public void windowClosed(WindowEvent windowEvent) {
System.out.println("Window closed: "
+ windowEvent.getWindow().getName());
}
};
for (int windowIndex = 2; windowIndex < 6; windowIndex++) {
String title = "Window " + windowIndex;
JFrame extraFrame = new JFrame(title);
extraFrame.setName(title);
extraFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
extraFrame.addWindowListener(windowAdapter);
extraFrame.setVisible(true);
}
frame.setVisible(true);
}
}
Consider this basic Swing program, consisting out of two buttons:
public class main {
public static void main(String[] args) {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton longAction = new JButton("long action");
longAction.addActionListener(event -> doLongAction());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(event -> System.out.println("this is a test"));
mainPanel.add(longAction);
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void doLongAction() {
SwingUtilities.invokeLater(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
System.out.println("Finished long action");
});
}
}
I want my second button testSystemOut to be usable while the first one is working on its long action (here, I put a 3 second sleep in it). I can do that by manually putting doLongAction() in a Thread and call start(). But I've read I should use SwingUtilities instead, which works exactly like EventQueue here. However, if I do so, my Button freezes for the duration of its action.
Why?
By using SwingUtilities.invokeLater, you are calling the enclosed code, including the Thread.sleep(...) call, on the Swing event thread, which is something you should never do since it puts the entire event thread, the thread responsible for drawing your GUI's and responding to user input, to sleep -- i.e., it freezes your application. Solution: use a Swing Timer instead or do your sleeping in a background thread. If you are calling long-running code and using a Thread.sleep(...) to simulate it, then use a SwingWorker to do your background work for you. Please read Concurrency in Swing for the details on this. Note that there is no reason for the SwingUtilities.invokeLater where you have it since the ActionListener code will be called on the EDT (the Swing event thread) regardless. I would however use SwingUtilities.invokeLater where you create your GUI.
e.g.,
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("this is a test");
}
});
mainPanel.add(new JButton(new LongAction("Long Action")));
mainPanel.add(new JButton(new TimerAction("Timer Action")));
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
});
}
#SuppressWarnings("serial")
public static class LongAction extends AbstractAction {
private LongWorker longWorker = null;
public LongAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
longWorker = new LongWorker(); // create a new SwingWorker
// add listener to respond to completion of the worker's work
longWorker.addPropertyChangeListener(new LongWorkerListener(this));
// run the worker
longWorker.execute();
}
}
public static class LongWorker extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 3 * 1000;
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(SLEEP_TIME);
System.out.println("Finished with long action!");
return null;
}
}
public static class LongWorkerListener implements PropertyChangeListener {
private LongAction longAction;
public LongWorkerListener(LongAction longAction) {
this.longAction = longAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// if the worker is done, re-enable the Action and thus the JButton
longAction.setEnabled(true);
LongWorker worker = (LongWorker) evt.getSource();
try {
// call get to trap any exceptions that might have happened during worker's run
worker.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
#SuppressWarnings("serial")
public static class TimerAction extends AbstractAction {
private static final int TIMER_DELAY = 3 * 1000;
public TimerAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
new Timer(TIMER_DELAY, new TimerListener(this)).start();
}
}
public static class TimerListener implements ActionListener {
private TimerAction timerAction;
public TimerListener(TimerAction timerAction) {
this.timerAction = timerAction;
}
#Override
public void actionPerformed(ActionEvent e) {
timerAction.setEnabled(true);
System.out.println("Finished Timer Action!");
((Timer) e.getSource()).stop();
}
}
}
Don't use SwingUtilities.invokeLater(...) when you want to execute some long-running code. Do that in a separate normal thread.
Swing is not multi-threaded, it's event-driven. Because of that there are methods like SwingUtilities.invokeLater(...). You have to use those methods if you want to alter Swing-Components from a different thread (since Swing is not thread-safe), for example if you want to change a Button's text.
Everything thats GUI-Related runs in that Swing-Thread, e.g. Cursor-Blinks, Messages from the OS, User Commands, etc.
Since its a single thread, every long running Code in this thread it will block your GUI.
If you just do some long-running code that isn't GUI-related, it shouldn't run in the Swing-Event-Thread, but in its own separated thread.
See
https://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html
for why Swing is not Multi-Threaded.
I have hit another wall. After getting my key input working, I have been racking my brains for hours, i want to create a pause function, so that if the same key is pressed again the timertask stops running (i.e the game is paused)
JPanel component = (JPanel)frame.getContentPane();
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
component.getActionMap().put("space", (new AbstractAction(){
public void actionPerformed(ActionEvent e){
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask(){
public void run(){
grid.stepGame();
}
},250, 250);
}}));
}
The problem is i cant use a global boolean isRunning var and switch it each time the key is pressed because the timerTask method in a nested class (so the boolean isRunning would have to be declared final to be accessed...). Any ideas on how to detect if the key is pressed again or if the game is already running so i can pause/cancel my timerTask.
Many Thanks Sam
Since this is a Swing game, you should be using a javax.swing.Timer or Swing Timer and not a java.util.Timer. By using a Swing Timer, you guarantee that the code being called intermittently is called on the EDT, a key issue for Swing apps, and it also has a stop method that pauses the Timer. You can also give your anonymous AbstractAction class a private boolean field to check if the key is being pressed for the first time or not.
Also, kudos and 1+ for using Key Bindings instead of a KeyListener.
e.g.,
JPanel component = (JPanel) frame.getContentPane();
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
component.getActionMap().put("space", (new AbstractAction() {
private boolean firstPress = true;
private int timerDelay = 250;
private javax.swing.Timer keyTimer = new javax.swing.Timer(timerDelay , new ActionListener() {
// Swing Timer's actionPerformed
public void actionPerformed(ActionEvent e) {
grid.stepGame();
}
});
// key binding AbstractAction's actionPerformed
public void actionPerformed(ActionEvent e) {
if (firstPress) {
keyTimer.start();
} else {
keyTimer.stop();
}
firstPress = !firstPress;
}
}));
Another useful option is to perform a repeating task on key press and stop it on key release, and this can be done easily by getting the keystrokes for on press and on release:
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true) // for key release
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false) // for key press
For example:
import java.awt.event.*;
import javax.swing.*;
public class SwingTimerEg2 {
private JFrame frame;
private Grid2 grid = new Grid2(this);
private JTextArea textarea = new JTextArea(20, 20);
private int stepCount = 0;
public SwingTimerEg2() {
frame = new JFrame();
textarea.setEditable(false);
frame.add(new JScrollPane(textarea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
setUpKeyBinding();
}
void setUpKeyBinding() {
final int timerDelay = 250;
final Timer keyTimer = new Timer(timerDelay, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
grid.stepGame();
}
});
JPanel component = (JPanel) frame.getContentPane();
final int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
final String spaceDown = "space down";
final String spaceUp = "space up";
component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), spaceDown);
component.getActionMap().put(spaceDown, (new AbstractAction() {
public void actionPerformed(ActionEvent e) {
keyTimer.start();
}
}));
component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), spaceUp);
component.getActionMap().put(spaceUp, (new AbstractAction() {
public void actionPerformed(ActionEvent e) {
keyTimer.stop();
}
}));
}
public void doSomething() {
textarea.append(String.format("Zap %d!!!%n", stepCount));
stepCount ++;
}
private static void createAndShowGui() {
new SwingTimerEg2();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class Grid2 {
private SwingTimerEg2 stEg;
public Grid2(SwingTimerEg2 stEg) {
this.stEg = stEg;
}
void stepGame() {
stEg.doSomething();
}
}
Easiest and dirty solution:
final boolean[] isRunning = new boolean[1];
You don't want to do that—but it works assuming proper synchronization around.
What would be better is
final AtomicBoolean isRunning = new AtomicBoolean();
What would be even better is to review the design once again: global state usually means, "global problems"
The final qualifier requirement can easily be avoided -- replace your inner method (which has the final requirement) with a call to a class method.
No you got the wrong idea about WHY you need final for anonymous classes! Final is only needed for local variables (well more exactly any variable that might have a live time shorter than the given object).
Hence a static variable in a class is perfectly fine and will work perfectly!
Edit: example:
public class Main {
interface Test {
void call();
}
public static volatile boolean running = true;
public static void main(String[] args) {
Test t = new Test() {
#Override
public void call() {
System.out.println(Main.running);
}
};
t.call();
running = false;
t.call();
}
}
Keep a reference to the Timer somewhere, say in your game class.
When the game is paused cancel the Timer.
This will cancel any currently scheduled tasks.
Then when the game is unpaused schedule the timer again as you have done above.
public class Game {
private Timer timer;
public void pause() {
if (timer != null) {
timer.pause();
}
}
public void startOrResumeGame() {
if (timer == null) {
timer = new Timer();
} else {
// Just in case the game was already running.
timer.cancel();
}
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
grid.stepGame();
}
}, 250, 250);
}
}
How can i exit only they new MainGame that i created from Main?
Where Main is having an original layer of game. And the MainGame was a dialog window (such as modal windows).
Main.java: (main code)
public class Main extends JWindow
{
private static JWindow j;
public static MainGame mp;
public static void main(String[] args)
{
new Thread(new Runnable()
{
public void run()
{
mp = new MainGame();
mp.runit();
//mp.stopit();
}
}).start();
j = new Main();
j.setVisible(true);
}
}
MainGame.java: (this was extended by Main, and i would like to quite this only).
public class MainGame extends JWindow
{
private static JWindow j;
public MainGame()
{
// some GUI ...
}
public static void runit()
{
j = new MainGame();
j.setVisible();
}
}
1) better would be implements CardLayout, as create Top-Level Container for new Window, then you'll only to switch betweens Cards
2) don't create lots of Top-Level Container on Runtime, because there are still in JVM memory untill current instance exist,
create required number of and re-use that, to avoiding possible memory lacks
then you have to call setVisible(false) and setVisible(true)
JWindow missed methods for setting setDefaultCloseOperation(Whatever);
3) if you'll create constructor public JWindow(Frame owner), then you'll call directly
SwingUtilities.getAccessibleChildrenCount() and SwingUtilities.getWindowAncestor()
import javax.swing.*;
import java.awt.*;
public class Testing {
private JFrame f = new JFrame("Main Frame");
private JWindow splashScreen = new JWindow();
public Testing() {
splashScreen = new JWindow(f);
splashScreen.getContentPane().setLayout(new GridBagLayout());
JLabel label = new JLabel("Splash Screen");
label.setFont(label.getFont().deriveFont(96f));
splashScreen.getContentPane().add(label, new GridBagConstraints());
splashScreen.pack();
splashScreen.setLocationRelativeTo(null);
splashScreen.setVisible(true);
new Thread(new Runnable() {
#Override
public void run() {
readDatabase();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}).start();
}
public void readDatabase() {
//simulate time to read/load data - 10 seconds?
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
public void createAndShowGUI() {
JLabel label = new JLabel("My Frame");
label.setFont(label.getFont().deriveFont(96f));
f.add(label);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
System.out.println("JFrame getAccessibleChildrenCount count -> "
+ SwingUtilities.getAccessibleChildrenCount(f));
System.out.println("JWindow getParent -> "
+ SwingUtilities.getWindowAncestor(splashScreen));
splashScreen.dispose();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Testing t = new Testing();
}
});
}
}
I did not go really into your design. but there is 'j.dispose();'.
this should work. here is the java documentation.
notice:
dispose(); - deletes the window from memory.
setVisibilty(false); - just hides it from the screen.
You can override the 'dispose()' function to do some stuff while the widow is closing (updating scores if its a game) but at the end of the overriden function you have to call 'super.dispose();' so the function of the class Window is called.
And the MainGame was a dialog window
But thats not what your code uses. You use a JWindow.
You should be using a JDialog for a modal window. Then you just dispose() the window.
I never get "paint" written to my command line window when I use Eclipse and Run->cmd to run the program. It works fine if I run System.out.print() from paintComponent in another program. Someone who can help?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUI extends JPanel implements KeyListener, ActionListener
{
private static final long serialVersionUID = 1L;
JFrame frmMain = new JFrame("Kodning");
JTextField text = new JTextField();
JPanel pan = new JPanel();
static char bokstav;
static int x=10, y=80;
boolean convert = false;
String s;
Timer t = new Timer(10, this);
public static void main(String[] args)
{
#SuppressWarnings("unused")
GUI g = new GUI();
}
public GUI()
{
frmMain.setSize(600, 120);
frmMain.setLayout(new GridLayout(2, 1));
frmMain.addWindowListener(hornStang());
frmMain.add(text);
frmMain.add(pan);
frmMain.setFocusable(true);
frmMain.setVisible(true);
frmMain.addKeyListener(this);
text.addKeyListener(this);
pan.addKeyListener(this);
t.start();
}
private static WindowAdapter hornStang()
{
return new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
};
}
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode()== KeyEvent.VK_ENTER)
{
System.out.println("dechifrera");
repaint();
deshiffrera(text.getText());
}
}
public void keyReleased(KeyEvent arg0){}
public void keyTyped(KeyEvent arg0){}
public void deshiffrera(String s)
{
s = this.s;
repaint();
}
#override
public void paintComponent(Graphics g)
{
System.out.println("paint");
for(int i=0;i<s.length();i++)
{
bokstav = s.charAt(i);
switch (bokstav)
{
case 'a':nere(g); hoger(g); prick(g, 0); break;
//en massa case
default:break;
}
x=x+12;
}
}
#Override
public void actionPerformed(ActionEvent e)
{
repaint();
}
}
The component must be added to a visible window/frame/component for it's paintComponent to be called.
GUI is only added as a KeyListener but is neither added to the JFrame, nor any other visible component in the code above. There is no reason for calling paintComponent since the component is not being displayed at all.
There are a number of issues with your code:
Your GUI panel is not in the frame (shouldn't it be added instead of pan?)
String s is uninitialized, which causes a NullPointerException
paint should be overridden instead of paintComponents
paint should not change the state of the component, because it can be called any time.
etc...
You probably miss the output of "System.out.println("paint");" ?
GUI-Apps under Windows cant write to the console (they dont have a console, because it would suck if every GUI-App would also open a black window).
There are two java-interpreters under windows: "javaw.exe" which is a GUI-App and silently discards any System.out-writes. And "java.exe" which is a console-app and allows writing to the console. Try to start your program with "java.exe"
I use this with AWT (not 100% sure whether it's working in Swing too...)
Graphics g = _yourcomponent_.getGraphics();
if (g != null) {
_yourcomponent_.paint(g);
// below the estimated code for Swing:
_yourcomponent_.paintComponent(g);
}