Why does SwingUtilities.invokeLater() cause JButton to freeze? - java

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.

Related

JProgressBar from Oracle

I've downloaded a small Java project from oracle website to create a progress bar.
I understand it, but I need to apply it in a different way, the application is creating a thread in the background so the progress bar can be updated accordingly (doInBackground()).
My question is, how can I replace this kind of process in the background in this application by a method from my application (method is just doing a kind of batch processing on a database), can someone help please?
Here is code by Oracle:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.util.Random;
public class ProgressBarDemo2 extends JPanel
implements ActionListener, PropertyChangeListener {
private JProgressBar progressBar;
private JButton startButton;
private JTextArea taskOutput;
private Task task;
class Task extends SwingWorker<Void, Void> {
/*
* Main task. Executed in background thread.
*/
#Override
public Void doInBackground() {
Random random = new Random();
int progress = 0;
//Initialize progress property.
setProgress(0);
//Sleep for at least one second to simulate "startup".
try {
Thread.sleep(1000 + random.nextInt(2000));
} catch (InterruptedException ignore) {}
while (progress < 100) {
//Sleep for up to one second.
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException ignore) {}
//Make random progress.
progress += random.nextInt(10);
setProgress(Math.min(progress, 100));
}
return null;
}
/*
* Executed in event dispatch thread
*/
public void done() {
Toolkit.getDefaultToolkit().beep();
startButton.setEnabled(true);
taskOutput.append("Done!\n");
}
}
public ProgressBarDemo2() {
super(new BorderLayout());
//Create the demo's UI.
startButton = new JButton("Start");
startButton.setActionCommand("start");
startButton.addActionListener(this);
progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
//Call setStringPainted now so that the progress bar height
//stays the same whether or not the string is shown.
progressBar.setStringPainted(true);
taskOutput = new JTextArea(5, 20);
taskOutput.setMargin(new Insets(5,5,5,5));
taskOutput.setEditable(false);
JPanel panel = new JPanel();
panel.add(startButton);
panel.add(progressBar);
add(panel, BorderLayout.PAGE_START);
add(new JScrollPane(taskOutput), BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
/**
* Invoked when the user presses the start button.
*/
public void actionPerformed(ActionEvent evt) {
progressBar.setIndeterminate(true);
startButton.setEnabled(false);
//Instances of javax.swing.SwingWorker are not reusuable, so
//we create new instances as needed.
task = new Task();
task.addPropertyChangeListener(this);
task.execute();
}
/**
* Invoked when task's progress property changes.
*/
public void propertyChange(PropertyChangeEvent evt) {
if ("progress" == evt.getPropertyName()) {
int progress = (Integer) evt.getNewValue();
progressBar.setIndeterminate(false);
progressBar.setValue(progress);
taskOutput.append(String.format(
"Completed %d%% of task.\n", progress));
}
}
/**
* Create the GUI and show it. As with all GUI code, this must run
* on the event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("ProgressBarDemo2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new ProgressBarDemo2();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
First, I'd recommend by defining a concept of a "progressable" state
public interface Progressable {
public void setProgress(int progress); // I prefer double, but we'll keep it inline with the rest of the API
}
Then, the entry point for your tasks would take a reference to Progressable
public class MySuperAwesomeLongRunningTask ... {
//...
private Progressable progressable;
public void performTask(Progressable progressable) {
this.prgressable = progressable
}
protected void methodThatDoesSomeWork() {
// Do some super duper work...
// calculate the progress of that work some how,
// based on your implementation...
int progress = ...;
progressable.setProgress(progress);
}
}
Then, create a SwingWorker which implements Progressable and calls your work...
class Task extends SwingWorker<Void, Void> implements Progressable {
private MySuperAwesomeLongRunningTask taskToBeDone;
public Task(MySuperAwesomeLongRunningTask taskToBeDone) {
self.taskToBeDone = taskToBeDone;
}
/*
* Main task. Executed in background thread.
*/
#Override
public Void doInBackground() {
taskToBeDone.performTask(this);
return null;
}
/*
* Executed in event dispatch thread
*/
public void done() {
// What ever you need to do...
}
}
Now, because SwingWorker already has a method called setProgress(int) it automatically conforms to Progressable (so long as you implement it), so when MySuperAwesomeLongRunningTask calls setProgress, it will actually be calling the SwingWorkers implementation.
This means, that the rest of the code basically remains the same, expect, I'd change
if ("progress" == evt.getPropertyName()) {
to
if ("progress".equals(evt.getPropertyName())) {
because comparing Strings with == is bad idea (and freaks me out :P)

Query on creating separate thread in java?

Below is the compiled program replica of actual problem code,
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Dummy {
public static boolean getUserCheck(int size, boolean Check) {
if (Check) {
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
if (ret > 0) {
System.out.println("User said No: " + ret);
return false;
} else if (ret <= 0) {
System.out.println("user said Yes: " + ret);
return true;
}
}
return true;
}
public static void workerMethod1() {
System.out.println("am worker method 1");
}
public static void workerMethod2() {
System.out.println("am worker method 2");
}
public static void main(String[] args) {
System.out.println("mainthread code line 1");
int size = 13;
boolean thresholdBreach = true;
if (getUserCheck(size, thresholdBreach)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod1();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod2();
}
});
}
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
}
}
where i would like to run the if{} block in main() on separate thread. Because these 2 lines,
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
need not wait for completion of if(){} block
Another problem is, experts recommend to run confirm-dialog methods on event thread.
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
Please help me!!!!
JOptionPane is a Swing method and should be called on the EDT, the Event Dispatch Thread, and only on this thread, and so it suggests that all your code above should be on the EDT, and that most of your SwingUtilities.invokeLater(new Runnable() calls are completely unnecessary. The only necessary ones will be the main one, where you launch your Swing GUI code, and any areas where Swing calls need to be made from within background threads. Again, if any of the above code is being made within background threads, then the JOptionPane should not be in that thread.
For more specific information in this or any other answer, please provide more specific information in your question. Let's end all confusion. The best way to get us to fully and quickly understand your problem would be if you were to to create and post a minimal example program, a small but complete program that only has necessary code to demonstrate your problem, that we can copy, paste, compile and run without modification.
I have a sneaking suspicion that a decent refactoring along MVC lines could solve most of your problems. Your code is very linear with its lines of code that must follow one another and its if blocks, and it is also tightly coupled with your GUI, two red flags for me. Perhaps better would be less linear code, more event and state-driven code, code where your background code interacts with the GUI via observer notification, and where the background code likewise responds to state changes in the GUI from control notification.
Your control needs two SwingWorkers, one to get the row count and the other to get the rest of the data if the user decides to do so. I'd add a PropertyChangeListener to the first SwingWorker to be notified when the row count data is ready, and then once it is, present it to the view for the user to select whether or not to proceed. If he decides to proceed, I'd then call the 2nd SwingWorker to get the main body of the data.
For example, a rough sketch of what I'm talking about:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerFooView extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 300;
private JProgressBar progressBar;
private JDialog dialog;
public SwingWorkerFooView() {
add(new JButton(new ButtonAction("Foo", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public boolean showOptionGetAllData(int numberOfRows) {
String message = "Number of rows = " + numberOfRows + ". Get all of the data?";
String title = "Get All Of Data?";
int optionType = JOptionPane.YES_NO_OPTION;
int result = JOptionPane.showConfirmDialog(this, message, title, optionType);
return result == JOptionPane.YES_OPTION;
}
public void showProgressBarDialog() {
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
Window window = SwingUtilities.getWindowAncestor(this);
dialog = new JDialog(window, "Hang on", ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel();
panel.add(progressBar);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
public void closeProgressBarDialog() {
dialog.dispose();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorkerFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerFooView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ButtonAction extends AbstractAction {
Workers workers = new Workers();
private SwingWorker<Integer, Void> firstWorker;
private SwingWorker<List<String>, Void> secondWorker;
private SwingWorkerFooView mainGui;
public ButtonAction(String name, SwingWorkerFooView mainGui) {
super(name);
this.mainGui = mainGui;
}
#Override
public void actionPerformed(ActionEvent e) {
firstWorker = workers.createFirstWorker();
firstWorker.addPropertyChangeListener(new FirstPropertyChangeListener());
firstWorker.execute();
mainGui.showProgressBarDialog();
}
private class FirstPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
int numberOfRows = firstWorker.get();
boolean getAllData = mainGui.showOptionGetAllData(numberOfRows);
if (getAllData) {
secondWorker = workers.createSecondWorker();
secondWorker.addPropertyChangeListener(new SecondPropertyChangeListener());
secondWorker.execute();
mainGui.showProgressBarDialog();
} else {
// user decided not to get all data
workers.cleanUp();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
private class SecondPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
List<String> finalData = secondWorker.get();
// display finalData in the GUI
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
class Workers {
// database object that may be shared by two SwingWorkers
private Object someDataBaseVariable;
private Random random = new Random(); // just for simulation purposes
private class FirstWorker extends SwingWorker<Integer, Void> {
#Override
protected Integer doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
// here we create our database object and check how many rows there are
int rows = random.nextInt(10 + 10); // this is just for demonstration purposes only
// here we create any objects that must be shared by both SwingWorkers
// and they will be saved in a field of Workers
someDataBaseVariable = "Fubar";
return rows;
}
}
private class SecondWorker extends SwingWorker<List<String>, Void> {
#Override
protected List<String> doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
List<String> myList = new ArrayList<>();
// here we go through the database filling the myList collection
return myList;
}
}
public SwingWorker<Integer, Void> createFirstWorker() {
return new FirstWorker();
}
public void cleanUp() {
// TODO clean up any resources and database stuff that will not be used.
}
public SwingWorker<List<String>, Void> createSecondWorker() {
return new SecondWorker();
}
}
The key to all of this is to not to think in a linear console program way but rather to use observer design pattern, i.e., listeners of some sort to check for change of state of both the GUI and the model.
It's essentially:
create worker
add observer to worker (property change listener)
execute worker
show progress bar dialog or notify user in some way that worker is executing.
The listener will be notified when the worker is done, and then you can query the worker (here via the get() method call) as to its end result.
Then the progress dialog can be closed
And the view can display the result or get additional information from the user.
Yes; SwingUtilities.invokeLater() simply places your runnable on the AWT event queue to be processed later, and it is safe to do so at any time.

Wait(), Notify(), timers and Jbuttons

I'm not sure how to even approach this but after doing some reading and a lot of attempts (failures) I've decided to ask the community for help. I have form A which opens and asks the user to enter a time to delay Form B from opening. Currently I am using sleep() to do this but now I would like to insert another dialog box to allow the user to interrupt the timer and bring up Form B before the timer runs out. I believe the correct way to do this is with wait() and notify() but I cannot seem to wrap my head around the numerous examples of producer and consumer models. Any help is appreciated.
A perfect job for javax.swing.Timer. Refer to How to Use Swing Timers for details. Here's an example to guide you in the right direction.
import java.awt.*;
import java.awt.event.*;
import javax.swing.Timer;
import javax.swing.*;
public class TimerDemo extends JFrame implements ActionListener {
private Timer timer;
private JButton jbDoSomethingDelayed;
private JButton jbDoItImmediately;
public TimerDemo() {
setLayout(new FlowLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Timer demo");
jbDoSomethingDelayed = new JButton("Do something with a delay");
jbDoItImmediately = new JButton("Do it. Do it NOW!");
add(jbDoSomethingDelayed);
add(jbDoItImmediately);
jbDoItImmediately.setEnabled(false);
timer = new Timer(0, this); // we override delay later
timer.setRepeats(false); // we don't want it firing repeatedly
jbDoSomethingDelayed.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String msg = "Enter delay and confirm dialog";
JSpinner spinner = new JSpinner(new SpinnerNumberModel(5, 1, 10, 1));
Object[] content = new Object[] {msg, spinner};
int showConfirmDialog = JOptionPane.showConfirmDialog(TimerDemo.this, content, "Choose", JOptionPane.OK_CANCEL_OPTION);
if (showConfirmDialog == JOptionPane.OK_OPTION) {
// the important part
timer.setInitialDelay(((Integer)spinner.getValue()) * 1000);
jbDoSomethingDelayed.setEnabled(false);
jbDoItImmediately.setEnabled(true);
timer.start();
}
}
});
jbDoItImmediately.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.stop();
onTimerTimeout();
}
});
pack();
setLocationRelativeTo(null);
}
public void actionPerformed(ActionEvent e) {
// called by timer on EDT, no worries here
onTimerTimeout();
}
private void onTimerTimeout() {
jbDoSomethingDelayed.setEnabled(true);
jbDoItImmediately.setEnabled(false);
JOptionPane.showConfirmDialog(this, "You've done it now. No, really...", "It is done", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
TimerDemo demo = new TimerDemo();
demo.setVisible(true);
}
});
}
}
The simplest way would be in doing something like this
Thread a = new Thread(new Runnable(){
public void run(){
//do whatever display
try{
Thread.sleep(timeToShowBform);
}
catch(InterruptedException ex){
//interrupted.
}finally{
//show form B
SwingUtilities.invokeLater(...)
}
});
class BRunnable implements Runnable{
public void run(){
//if clicked, then this runnable is called.
a.interrupt();
}
}
The thread a suppose is blocked at sleep, then on calling a.interrupt() it wakes a.

Thread interruption and ActionListener Java

I have a function graphics() that creates my JFrame and two JRadioButtons and adds ActionListeners to them. This graphics is called from main() and graphics itself calls game().
public void game() throws Exception
{
jTextArea1.setLineWrap(true);
jTextArea1.setWrapStyleWord(true);
jTextArea1.setText("This is private information.");
jRadioButton1.setVisible(true);
jRadioButton2.setVisible(true);
try {
t.sleep(40000);
repaint();
} catch (InterruptedException e) {
// We've been interrupted: no more messages.
return;
}
After displaying "This is private information." in the text Area, I want the program execution to pause for 40 seconds, or until the user presses the JRadioButton, whichever is earlier. So I added an ActionListener and called t.interrupt() inside it.
private void jRadioButton1ActionPerformed(java.awt.event.ActionEvent evt) {
t.interrupt();
jRadioButton1.setVisible(false);
jRadioButton2.setVisible(false);
//System.out.println(t.interrupted());
jTextArea1.setText("Please wait...");
}
However, even after choosing the JRadioButton which should trigger the interrupt, that does not happen and t.interrupted returns false.
Any help would be appreciated.
Never, ever call Thread.sleep(...) on the Swing event thread as you will freeze the thread and effectively freeze your program. The solution is to consider use of a Swing Timer for the time-dependent portion of your requirement and using a SelectionListener for the JCheckBox or JRadioButton requirement.
For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
public class PausingExecution extends JPanel {
private static final String SELECTED_TEXT = "Snafus are Better!!!";
private static final String UNSELECTED_TEXT = "Fubars Rule!!";
private static final String TIMES_UP = "Time's Up!!!!";
private static final int TIMER_DELAY = 10 * 1000;
private JTextField messageField = new JTextField(UNSELECTED_TEXT, 10);
private JCheckBox checkBox = new JCheckBox("Click Me");
public PausingExecution() {
add(messageField);
add(checkBox);
checkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent iEvt) {
if (iEvt.getStateChange() == ItemEvent.SELECTED) {
messageField.setText(SELECTED_TEXT);
} else {
messageField.setText(UNSELECTED_TEXT);
}
}
});
Timer mySwingTimer = new Timer(TIMER_DELAY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
messageField.setText(TIMES_UP);
checkBox.setEnabled(false);
}
});
mySwingTimer.setRepeats(false);
mySwingTimer.start();
}
private static void createAndShowGui() {
PausingExecution mainPanel = new PausingExecution();
JFrame frame = new JFrame("PausingExecution");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Java Swing: How do I wake up the main thread from the event-dispatch thread?

I want to cause the "main thread" (the thread started which runs main()) to do some work from the actionPerformed() method of a button's ActionListener, but I do not know how to achieve this.
A little more context:
I am currently programming a 2D game using Swing (a flavour of Tetris).
When the application starts, a window opens which displays the main menu of the game.
The user is presented several possibilities, one of them is to start the game by pushing a "Start" button, which causes the game panel to be displayed and triggers the main loop of the game.
To be able to switch between the two panels (that of the main menu and that of the game), I am using a CardLayout manager, then I can display one panel by calling show().
The idea is that I would like my start button to have a listener that looks like this:
public class StartListener implements ActionListener {
StartListener() {}
public void actionPerformed(ActionEvent e) {
displayGamePanel();
startGame();
}
}
but this does not work because actionPerformed() is called from the event-dispatch thread, so the call to startGame() (which triggers the main loop: game logic update + repaint() call at each frame) blocks the whole thread.
The way I am handling this right now is that actionPerformed() just changes a boolean flag value: public void actionPerformed(ActionEvent e) {
startPushed = true;
}
which is then eventually checked by the main thread:
while (true) {
while (!g.startPushed) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
g.startPushed = false;
g.startGame();
}
But I find this solution to be very inelegant.
I have read the Concurrency in Swing lesson but I am still confused (should I implement a Worker Thread – isn't that a little overkill?). I haven't done any actual multithreading work yet so I am a little lost.
Isn't there a way to tell the main thread (which would be sleeping indefinitely, waiting for a user action) "ok, wake up now and do this (display the game panel and start the game)"?.
Thanks for your help.
EDIT:
Just to be clear, this is what my game loop looks like:
long lastLoopTime = System.currentTimeMillis();
long dTime;
int delay = 10;
while (running) {
// compute the time that has gone since the last frame
dTime = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();
// UPDATE STATE
updateState(dTime);
//...
// UPDATE GRAPHICS
// thread-safe: repaint() will run on the EDT
frame.repaint()
// Pause for a bit
try {
Thread.sleep(delay);
} catch (Exception e) {}
}
This doesn't make sense:
but this does not work because actionPerformed() is called from the event-dispatch thread, so the call to startGame() (which triggers the main loop: game logic update + repaint() call at each frame) blocks the whole thread.
Since your game loop should not block the EDT. Are you using a Swing Timer or a background thread for your game loop? If not, do so.
Regarding:
while (true) {
while (!g.startPushed) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
g.startPushed = false;
g.startGame();
}
Don't do this either, but instead use listeners for this sort of thing.
e.g.,
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class GameState extends JPanel {
private CardLayout cardlayout = new CardLayout();
private GamePanel gamePanel = new GamePanel();
private StartPanel startpanel = new StartPanel(this, gamePanel);
public GameState() {
setLayout(cardlayout);
add(startpanel, StartPanel.DISPLAY_STRING);
add(gamePanel, GamePanel.DISPLAY_STRING);
}
public void showComponent(String displayString) {
cardlayout.show(this, displayString);
}
private static void createAndShowGui() {
GameState mainPanel = new GameState();
JFrame frame = new JFrame("GameState");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class StartPanel extends JPanel {
public static final String DISPLAY_STRING = "Start Panel";
public StartPanel(final GameState gameState, final GamePanel gamePanel) {
add(new JButton(new AbstractAction("Start") {
#Override
public void actionPerformed(ActionEvent e) {
gameState.showComponent(GamePanel.DISPLAY_STRING);
gamePanel.startAnimation();
}
}));
}
}
class GamePanel extends JPanel {
public static final String DISPLAY_STRING = "Game Panel";
private static final int PREF_W = 500;
private static final int PREF_H = 400;
private static final int RECT_WIDTH = 10;
private int x;
private int y;
public void startAnimation() {
x = 0;
y = 0;
int timerDelay = 10;
new Timer(timerDelay , new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
repaint();
}
}).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
you should be using a SwingWorker this will execute the code in doInBackground() in a background thread and the code in done() in the EDT after doInBackground() stops
The easiest way: use a CountDownLatch. You set it to 1, make it available in the Swing code by any means appropriate, and in the main thread you await it.
You can consider showing a modal dialog with the game panel using SwingUtilities.invokeAndWait() so that when the dialog is closed the control returns back to main thread.
You can make all code except the EDT run on single thread execution service and then just post runnables whenever you need some code executed.

Categories