Using Java wait() and notify() without freezing Swing GUI - java

I understand that Swing GUIs themselves use Threads, but I am trying to use a separate thread to run my simulation. I created a class that implements Runnable and uses a custom Thread as most simple Runnable examples do. My run() method basically runs my simulation, updating every second (which works great), but I'm now trying to implement buttons that can pause/resume the simulation. My Start button successfully starts the thread, and my Pause button successfully pauses the thread. However, when Pause is selected, the entire GUI is paused, and you can see the button as still being selected and I am unable to select any buttons or interact with the GUI at all. As soon as I call wait() on my custom thread, my entire GUI halts, even though I'm using a Thread separate from this GUI. Why does calling wait() freeze up my GUI? How can I pause just this specific Thread and not the entire GUI?
Please note that the Start button should be what makes the program resume. Here's my code for the GUI:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GridFrame extends JFrame {
private static final long serialVersionUID = 2857470112009359285L;
private MyGridPanel grid;
private Simulation sim;
GridFrame(Simulation sim, int w, int h, int rows, int cols) {
this.sim = sim;
setTitle("Simulation");
setSize(w, h);
grid = new MyGridPanel(w, h, rows, cols);
add(grid, BorderLayout.CENTER);
//Build bottom panel
JPanel buttons = new JPanel();
buttons.setLayout(new GridLayout(1,3));
JButton start = new JButton("Start");
JButton pause = new JButton("Pause");
JButton reset = new JButton("Reset");
start.setActionCommand("Start");
start.addActionListener(new ButtonActionListener());
pause.setActionCommand("Pause");
pause.addActionListener(new ButtonActionListener());
reset.setActionCommand("Reset");
reset.addActionListener(new ButtonActionListener());
buttons.add(start);
buttons.add(pause);
buttons.add(reset);
add(buttons, BorderLayout.SOUTH);
}
public MyGridPanel getGrid(){
return grid;
}
private class ButtonActionListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
switch(e.getActionCommand()){
case "Start":
System.out.println("Start");
sim.start();
break;
case "Pause":
System.out.println("Pause");
sim.pause();
break;
case "Reset":
System.out.println("Reset");
break;
}
}
}
}
And here is my Runnable:
public class Simulation implements Runnable{
private Thread t;
private GridFrame frame;
private boolean paused;
public Simulation(){
frame = new GridFrame(this, 300, 300, 10, 10);
frame.setVisible(true);
paused = true;
}
public void start () {
if(t == null){
//Thread has not been created. Simulation has not started to run yet
System.out.println("Starting thread.");
t = new Thread(this);
t.start();
paused = false;
}
else if(paused){
//Simulation and thread already started to run but was paused. This should use notify() to resume the thread.
resume();
paused = false;
}
}
public void resume(){
synchronized(t){
t.notify();
}
}
public void pause(){
synchronized(t){
try {
t.wait();
paused = true;
} catch (InterruptedException e) {
System.out.println("Exception when trying to pause simulation");
e.printStackTrace();
}
}
}
#Override
public void run() {
while(true){
frame.getGrid().step();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread interrupted while simulation was running.");
e.printStackTrace();
}
}
}
public static void main(String[] a) {
Simulation s = new Simulation();
}
}

Calling wait and notify on a Thread object behaves no differently than it does on any other object. Specifically, as you've noticed, it does not send a signal to the executing thread that it should pause, but rather it will block the calling thread (your UI thread in this case) until it receives a notify message.
You will need to implement a messaging system (such as a blocking queue) from your UI thread to your background thread in order to get what you want.

Related

How to deliver object to a long-running SwingWorker from the EDT?

How can the EDT communicate to an executing SwingWorker? There a lot of ways for the SwingWorker to communicate information back to the EDT - like publish/process and property changes but no defined way (that I have seen) to communicate in the other direction. Seems like good old Java concurrent inter-thread communication would be the way to go via wait() and notify(). This doesn't work. I'll explain later. I actually got it to work but it uses an ugly polling mechanism. I feel like there should be a better way. Here is the process that I am trying to accomplish:
From the main Swing UI (EDT) a user starts a SwingWorker long-running task (the engine).
At some point the engine needs information from the EDT so it communicates this back to the EDT. this could be done through publish/process update of a visible UI component. Importantly, this step DOES NOT block the EDT because other things are also going on.
The engines blocks waiting for an answer.
At some point the user notices the visual indication and provides the required information via some UI (EDT) functionality - like pressing a Swing button.
The EDT updates an object on the engine. Then "wakes up" the engine.
The engine references the updated object and continues to process.
The problem I have with wait()/notify() is that in step 3 any invocation of wait() in doInBackground() causes the done() method to be immediately fired and the SwingWorker to be terminated.
I was able to get the above process to work by using an ugly sleep() loop in doInBackground():
for (;;)
{
Thread.sleep(10);
if (fromEDT != null)
{
// Process the update from the EDT
System.out.println("From EDT: " + fromEDT);
fromEDT = null;
break;
}
}
What this really is that in step 5 the engine wakes itself up and checks for updates from the EDT.
Is this the best way to do this? I kind of doubt it.
The following is an mre demonstrating a SwingWorker paused and waiting for user's input:
import java.awt.*;
import java.util.List;
import javax.swing.*;
public class SwingWorkerWaitDemo {
public static void creategui(){
JFrame f = new JFrame("SwingWorker wait Demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.add(new MainPanel());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
creategui();
}
}
class MainPanel extends JPanel {
private static final String BLANK = " ";
private MyWorker swingWorker;
private final JLabel output, msg;
private final JButton start, stop, respond;
MainPanel() {
setLayout(new BorderLayout(2, 2));
start = new JButton("Start");
start.addActionListener(e->start());
stop = new JButton("Stop");
stop.setEnabled(false);
stop.addActionListener(e->stop());
JPanel ssPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
ssPane.add(start); ssPane.add(stop);
add(ssPane, BorderLayout.PAGE_START);
output = new JLabel(BLANK);
JPanel outputPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
outputPane.add(output);
add(outputPane, BorderLayout.CENTER);
msg = new JLabel(BLANK);
respond = new JButton("Respond");
respond.addActionListener(e->respond());
respond.setEnabled(false);
JPanel responsePane = new JPanel();
responsePane.add(msg); responsePane.add(respond);
add(responsePane, BorderLayout.PAGE_END);
}
#Override
public Dimension getPreferredSize(){
return new Dimension(400, 200);
}
private void start() {
start.setEnabled(false);
stop.setEnabled(true);
swingWorker = new MyWorker();
swingWorker.execute();
}
private void stop() {
stop.setEnabled(false);
swingWorker.setStop(true);
}
private void message(String s){
msg.setText(s);
}
private void clearMessage(){
msg.setText(BLANK);
}
private void askForUserResponse(){
respond.setEnabled(true);
message("Please respond " );
}
private void respond(){
clearMessage();
respond.setEnabled(false);
swingWorker.setPause(false);
}
class MyWorker extends SwingWorker<Integer, Integer> {
private boolean stop = false;
private volatile boolean pause = false;
#Override
protected Integer doInBackground() throws Exception {
int counter = 0;
while(! stop){
publish(counter++);
if(counter%10 == 0) {
pause = true;
askForUserResponse();
while(pause){ /*wait*/ }
}
Thread.sleep(500);
}
return counter;
}
#Override
protected void process(List<Integer> chunks) {
for (int i : chunks) {
output.setText(String.valueOf(i));
}
}
#Override
protected void done() {
message("All done");
}
void setStop(boolean stop) {
this.stop = stop;
}
void setPause(boolean pause) {
this.pause = pause;
}
}
}

Stop a loop in java with graphical interface (buttons)

I created a graphic interface in java and 2 buttons.
My aim :
1) When I click on the first button, having a loop in which different tasks are processed (Button "Start"). Between each loop there is a stop of 10 seconds
2) When I click on the second button, the loop is processed immediately one last time but then stopped.
(I also would like to make a pop up showing that it has been stopped but that's not the main question, I think I can do it.)
I tried the following code, but first I think they are more simple ways to sort my problem. Plus I can compile but it doesn't work, the loop is not stopped, the window crashes:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
globalStop="Run";
while (globalStop.equals("Run")) {
System.out.println("GO");
// Other stuff
// For the break ?
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Logger.getLogger(main.class.getName()).log(Level.SEVERE, null, ex);
}
}
System.out.println("done");
}
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
globalStop = "Stop";
System.out.println("Bouton2");
}
I hope I was clear enough, if that is not the case, please let me know and I will rephrase.
Thank you all in advance for your help.
I wondered how long it would take me to create a United States type traffic signal GUI. It took 75 minutes. I was able to create the GUI quickly because a lot of Swing is boilerplate. Once you create one GUI, you can copy some of the classes for your next GUI.
Here's an image of the traffic signal GUI.
When you press the Start button, the traffic signal will cycle from green to yellow to red. The traffic signal will cycle forever, until you press the Stop button.
When you press the Stop button, the traffic signal will turn red. It will stay red forever, until you press the Start button.
When you press the Start button while the traffic signal is cycling, the green to yellow to red cycle starts over.
Basically, the following steps show you how to create any Swing GUI. I didn't create the code in this order, but it makes sense to explain the code in a logical order. So, let's dig into the code.
This is the model class for the GUI. Every GUI needs to have it's own model, separate from the model of the application. For this GUI, the model is simple.
package com.ggl.traffic.signal.model;
import java.awt.Dimension;
public class TrafficSignalModel {
public static final int RED_LIGHT_TIME = 15;
public static final int YELLOW_LIGHT_TIME = 5;
public static final int GREEN_LIGHT_TIME = 10;
public static final Dimension LIGHT_SIZE = new Dimension(32, 32);
}
We set the signal light times in the model, as well as the size of the traffic lights.
For a more complicated GUI, we would keep track of the field values in the model.
Next, we have the main class of the traffic signal GUI.
package com.ggl.traffic.signal;
import javax.swing.SwingUtilities;
import com.ggl.traffic.signal.view.TrafficSignalFrame;
public class TrafficSignal implements Runnable {
#Override
public void run() {
new TrafficSignalFrame();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new TrafficSignal());
}
}
This class ensures that the traffic signal GUI is on the Swing event thread. That's all this class does. You can see how you can copy this class to start any GUI.
Next, we have the Frame class of the GUI.
package com.ggl.traffic.signal.view;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class TrafficSignalFrame {
protected ButtonPanel bPanel;
protected JFrame frame;
protected TrafficSignalPanel tsPanel;
public TrafficSignalFrame() {
createPartControl();
}
protected void createPartControl() {
tsPanel = new TrafficSignalPanel();
bPanel = new ButtonPanel();
bPanel.setTrafficSignalPanel(tsPanel);
frame = new JFrame();
frame.setTitle("Traffic Signal");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
frame.setLayout(new FlowLayout());
frame.add(bPanel.getPanel());
frame.add(tsPanel.getPanel());
frame.pack();
// frame.setBounds(100, 100, 400, 200);
frame.setVisible(true);
}
public void exitProcedure() {
frame.dispose();
System.exit(0);
}
public JFrame getFrame() {
return frame;
}
}
This class is boilerplate, except for the particular JPanels that will make up the GUI. If your JFrame has a JMenu, this would be the place to attach your JMenu to your JFrame.
Notice that I did not extend JFrame to make this class. The only time you extend a Swing component is when you're overriding one or more of the component's methods. If I need the actual JFrame, I call the getFrame() method. Using Swing components rather than extending Swing components keeps my methods separate from the Swing methods.
Next, we'll look at the traffic signal light panel. This panel makes up one of the 3 lights in the traffic signal.
package com.ggl.traffic.signal.view;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class TrafficSignalLightPanel extends JPanel {
private static final long serialVersionUID = 1L;
protected boolean lightOn;
protected Color lightColor;
protected Color darkColor;
public TrafficSignalLightPanel(Color lightColor) {
this.lightColor = lightColor;
this.darkColor = Color.WHITE;
this.lightOn = false;
}
public void setLightOn(boolean lightOn) {
this.lightOn = lightOn;
this.repaint();
}
#Override
public void paintComponent(Graphics g) {
if (lightOn) {
g.setColor(lightColor);
} else {
g.setColor(darkColor);
}
g.fillRect(0, 0, getWidth(), getHeight());
}
}
This class extends JPanel, because we want to override the paintComponent method. This is a simple class. All it does is paint the panel a color, or white.
Next, we'll look at the traffic signal panel. This panel creates 3 light panels and arranges them in a vertical row.
package com.ggl.traffic.signal.view;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.border.Border;
import com.ggl.traffic.signal.model.TrafficSignalModel;
public class TrafficSignalPanel {
protected JPanel panel;
protected TrafficSignalLightPanel redLight;
protected TrafficSignalLightPanel yellowLight;
protected TrafficSignalLightPanel greenLight;
public TrafficSignalPanel() {
createPartControl();
}
protected void createPartControl() {
Border border = BorderFactory.createLineBorder(Color.BLACK, 4);
redLight = new TrafficSignalLightPanel(Color.RED);
redLight.setBorder(border);
redLight.setPreferredSize(TrafficSignalModel.LIGHT_SIZE);
yellowLight = new TrafficSignalLightPanel(Color.YELLOW);
yellowLight.setBorder(border);
yellowLight.setPreferredSize(TrafficSignalModel.LIGHT_SIZE);
greenLight = new TrafficSignalLightPanel(Color.GREEN);
greenLight.setBorder(border);
greenLight.setPreferredSize(TrafficSignalModel.LIGHT_SIZE);
panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.setPreferredSize(
new Dimension(TrafficSignalModel.LIGHT_SIZE.width + 10,
TrafficSignalModel.LIGHT_SIZE.height * 3 + 25));
panel.add(redLight);
panel.add(yellowLight);
panel.add(greenLight);
}
public JPanel getPanel() {
return panel;
}
public TrafficSignalLightPanel getRedLight() {
return redLight;
}
public TrafficSignalLightPanel getYellowLight() {
return yellowLight;
}
public TrafficSignalLightPanel getGreenLight() {
return greenLight;
}
}
A fairly straightforward creation of a JPanel from 3 JPanels. I set the preferred size of the JPanel so the lights will be in a vertical row.
Next, we'll look at the button panel. You can pretty much copy this code into any GUI that has a button panel.
package com.ggl.traffic.signal.view;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
import com.ggl.traffic.signal.thread.TrafficSignalCycle;
public class ButtonPanel {
protected JButton startButton;
protected JButton stopButton;
protected JPanel panel;
protected TrafficSignalCycle thread;
protected TrafficSignalPanel tsPanel;
public ButtonPanel() {
this.thread = null;
createPartControl();
}
protected void createPartControl() {
panel = new JPanel();
panel.setLayout(new FlowLayout());
startButton = new JButton("Start");
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
if (thread != null) {
thread.stopRunning();
}
tsPanel.getRedLight().setLightOn(false);
tsPanel.getYellowLight().setLightOn(false);
tsPanel.getGreenLight().setLightOn(false);
thread = new TrafficSignalCycle(tsPanel);
thread.start();
}
});
panel.add(startButton);
stopButton = new JButton("Stop");
stopButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
if (thread != null) {
thread.stopRunning();
thread = null;
}
tsPanel.getRedLight().setLightOn(true);
tsPanel.getYellowLight().setLightOn(false);
tsPanel.getGreenLight().setLightOn(false);
}
});
panel.add(stopButton);
setButtonSizes(startButton, stopButton);
}
protected void setButtonSizes(JButton ... buttons) {
Dimension preferredSize = new Dimension();
for (JButton button : buttons) {
Dimension d = button.getPreferredSize();
preferredSize = setLarger(preferredSize, d);
}
for (JButton button : buttons) {
button.setPreferredSize(preferredSize);
}
}
protected Dimension setLarger(Dimension a, Dimension b) {
Dimension d = new Dimension();
d.height = Math.max(a.height, b.height);
d.width = Math.max(a.width, b.width);
return d;
}
public void setTrafficSignalPanel(TrafficSignalPanel tsPanel) {
this.tsPanel = tsPanel;
}
public JPanel getPanel() {
return panel;
}
}
The button actions were simple enough that I could keep them in the button panel. If you want, you can code separate action classes.
Finally, here's the code that runs the traffic light cycle. It's an extension of the Thread class, so it can be run in a separate thread from the GUI. It's always a good idea to do work in threads separate from the GUI thread.
package com.ggl.traffic.signal.thread;
import javax.swing.SwingUtilities;
import com.ggl.traffic.signal.model.TrafficSignalModel;
import com.ggl.traffic.signal.view.TrafficSignalLightPanel;
import com.ggl.traffic.signal.view.TrafficSignalPanel;
public class TrafficSignalCycle extends Thread {
protected boolean isRunning;
protected boolean isFinished;
protected TrafficSignalPanel tsPanel;
public TrafficSignalCycle(TrafficSignalPanel tsPanel) {
this.tsPanel = tsPanel;
this.isRunning = true;
this.isFinished = false;
}
#Override
public void run() {
while (isRunning) {
signalLightOn(tsPanel.getGreenLight(), TrafficSignalModel.GREEN_LIGHT_TIME);
signalLightOn(tsPanel.getYellowLight(), TrafficSignalModel.YELLOW_LIGHT_TIME);
signalLightOn(tsPanel.getRedLight(), TrafficSignalModel.RED_LIGHT_TIME);
}
this.isFinished = true;
}
protected void signalLightOn(TrafficSignalLightPanel light, int seconds) {
if (isRunning) {
setLightOn(light, true);
}
for (int i = 0; i < 1000 && isRunning; i++) {
try {
Thread.sleep(1L * seconds);
} catch (InterruptedException e) {
}
}
setLightOn(light, false);
}
protected void setLightOn(final TrafficSignalLightPanel light,
final boolean isLightOn) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
light.setLightOn(isLightOn);
}
});
}
public void stopRunning() {
this.isRunning = false;
while (!isFinished) {
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
}
}
}
}
The method that actually changes the color of the signal light must execute in the Swing event thread. That's what the setLightOn method does by calling SwingUtilities.
The timing loop is a bit complicated because we want to be able to stop the thread in a few milliseconds. The isFinished boolean ensures that the thread is stopped completely, so that the lights can be set.
This is a fairly long answer, but I hope it's helpful to anyone creating a Swing GUI.
You shouldn't be looping within the UI thread, nor telling it to sleep. Fundamentally you should keep the UI thread as free as possible.
If you need something to occur on a regular basis in a Swing UI in the UI thread, use a Swing Timer.
It's unclear what you're doing in the "other stuff" however - it's possible that you should be doing that in a different thread entirely and using (say) an AtomicBoolean to indicate when you want to stop.
1. You should always keep the UI thread for UI work and Non-UI thread for Non-UI work.
2. In Java GUI, the main() is not Long lived, after assigning the construction of GUI to the Event Dispatcher Thread, the main() quits, and now its EDT's responsibility handle the GUI.
3. So when you click the buttons, and the work you are doing is doing some heavy process or its time consuming....then span a Separate thread.
4. You can use Thread or SwingWorker.
Example:
Button b = new Button("Click me");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
Thread t = new Thread(new Runnable(){
public void run(){
// Do the Heavy Processing work.....
}
});
t.start();
}
});
The easy but dirty way:
Multi-thread your program and have one thread do your loop and a second thread monitor your buttons. Have the button change your globalStop variable
The not so easy but cleaner way:
Make the button throw an interrupt to change the value. After the interrupt the for loop will continue to the end.

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.

strange bug - how to pause a java program?

I'm trying to:
display a text in a jLabel,
wait for two seconds,
then write a new text in the jLabel
this should be simple, but I get a strange bug:
the first text is never written, the application just waits for 2 seconds and then displays the final text. here is the example code:
private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {
displayLabel.setText("Clicked!");
// first method with System timer
/*
long t0= System.currentTimeMillis();
long t1= System.currentTimeMillis();
do{
t1 = System.currentTimeMillis();
}
while ((t1 - t0) < (2000));
*/
// second method with thread.sleep()
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {}
displayLabel.setText("STOP");
}
with this code, the text "Clicked!" is never displayed. I just get a 2 seconds - pause and then the "STOP" text.
I tried to use System timer with a loop, or Thread.sleep(), but both methods give the same result.
Just to provide more background on Andrew Thompson's comment: the EDT is responsible for handling gui updates. If you block it using Thread.sleep(...) those updates are blocked as well. That's why you don't see the first text - the EDT just can't do the update on the label.
Here's a runnable example which does what you're after. As Andrew Thompson's comment stated, a SwingWorker is a good way to approach this problem.
The basic principal is to never block the Event Dispatch Thread. That's the thread responsible for repainting the GUI and responding to user interaction, so if you do something computationally expensive on the EDT, your GUI will stop responding.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
public class ButtonTest {
public static void main(String[] args) {
// create a frame and a button
JFrame frame = new JFrame();
final JButton button = new JButton("Button");
frame.add(button);
// add an action listener to the button
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
// change the button text right away
button.setText( "Clicked" );
// create a SwingWorker which simply waits 2000 milliseconds
// simulating a computation being performed
SwingWorker<String, Object> worker = new SwingWorker<String, Object>() {
#Override
public String doInBackground() {
// it's safe to call Thread.sleep( ) here
// doInBackground is executed on a separate worker
// thread
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return "Done";
}
#Override
protected void done() {
// done() is executed back on the Swing thread
// so it's safe to updated the state of the button
try {
button.setText(get());
} catch (Exception e) { }
}
};
// run the worker
worker.execute();
}
});
frame.setSize( 300, 300 );
frame.setVisible( true );
}
}
You are messing with the event dispatcher thread.
That will cause un-expected UI behavior as you are seeing. If you plan to do these type of animations, make sure to read up on what #Andrew Thompson suggested and also, see if you can read this - Filthy rich clients
Better to use a Swing Timer as shown in curde-example below:(yes, it is crude, I did not worry about stopping the timer etc):
public class DelayTest extends JPanel{
JLabel messageLabel = new JLabel();
JButton actionButton = new JButton("Click Me");
String[] messages = {"Clicked", "Stop!"};
int i=0;
public DelayTest(){
super();
add(messageLabel);
add(actionButton);
actionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if(i<=1)
messageLabel.setText(messages[i++]);
}
});
timer.start();
}
});
}
}
Edit
Why not stop the Timer:
#Override
public void actionPerformed(ActionEvent evt) {
if (i <= 1) {
messageLabel.setText(messages[i++]);
} else {
((Timer)evt.getSource()).stop();
}
}
});

java swing thread problem

In my java swing application am having a Jframe and Jlabel for displaying current time.
here am using a thread for displaying time in jlablel which is added to the frame.my doubt is that when i dispose the jframe what will happen to the thread whether its running or stopped.
If you have NOT marked your thread as daemon by calling yourThread.setDaemon(true), it will keep running even if main thread in your application has finished. Remember you have to call setDaemon before starting the thread.
Refer my answer to some previous question for details.
The correct way in your case, I believe, would be you maintain a 'stop' flag which is watched by your timer thread. Timer thread should exit on reading this flag as 'false'. You can add a WindowListener to your jframe and on the window closed event set the 'stop' flag to true
Heres example code for what I am suggesting :
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class JFrameTest {
public static void main(String[] args) {
final Timer t = new Timer();
t.start();
JFrame jf = new JFrame("GOPI");
jf.setVisible(true);
jf.setSize(100, 100);
jf.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
t.stopTimer();
}
});
System.out.println("JFrameTest.main() DONE");
}
}
class Timer extends Thread {
boolean stop = false;
#Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (stop)
break;
System.out.println("Counting :" + i);
}
System.out.println("Timer exit");
}
public void stopTimer() {
stop = true;
}
}
Your thread will keep running.
You need to either do as suggested by Gopi or you could use System.exit(0) in close operation of your JFrame.
NOTE: I am assuming here that Your application needs to end if this Frame is closed.

Categories