Swing Countdown from 5 to 0 - java

I am creating a Java Swing game and before every new game I would like to have a frame show up and countdown from 5 to 0 seconds.
While this is happening I would like the game in the background to wait until the countdown is complete. What would be the best way to make the game in the background wait?
I have tried Thread.sleep but this causes the The Event Dispatch Thread to sleep and the GUI not to update. However, it works the first time i run it but not the second.
Thankful for your help.
public class CountdownPresenterPanel {
JFrame mainFrame;
int currentNumber = 5;
JLabel textLabel;
CountdownPresenterPanel() {
mainFrame = new JFrame();
textLabel = new JLabel(String.valueOf(currentNumber), SwingConstants.CENTER);
textLabel.setFont(new Font("Arial", Font.BOLD, 55));
textLabel.setVerticalAlignment(SwingConstants.CENTER);
mainFrame.add(textLabel);
mainFrame.setUndecorated(true); // Remove bar including close, minimize
mainFrame.setSize(600,300);
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(currentNumber > 0) {
currentNumber--;
textLabel.setText(String.valueOf(currentNumber));
} else {
mainFrame.setVisible(false);
mainFrame.dispose();
}
}
});
timer.setRepeats(true);
timer.start();
}
}
Solution
import java.awt.*;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
public class CountdownPresenterPanel {
private int currentNumber = 5;
private JLabel textLabel;
private final JDialog dialog;
CountdownPresenterPanel() {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(currentNumber > 0) {
currentNumber--;
textLabel.setText(String.valueOf(currentNumber));
} else {
dialog.dispose();
}
}
});
Frame window = new Frame();
dialog = new JDialog(window, "Alert", true);
textLabel = new JLabel(String.valueOf(currentNumber), SwingConstants.CENTER);
textLabel.setFont(new Font("Arial", Font.BOLD, 55)); // Increase the font-size
dialog.add(textLabel);
dialog.setSize(400, 200);
dialog.setLocationRelativeTo(null);
timer.setRepeats(true);
timer.start();
dialog.setUndecorated(true);
dialog.setVisible(true);
}
}

Don't use a JFrame, use some kind of modal dialog
Take a look at How to Make Dialogs for more details

Seems that you want something like a SplashScreen. Have you looked to the SplasScreen Tutorial from Oracle? There Thread.sleep() is used safely and it updates the graphics correctly. I think it to be your unique solution. The only way to let the undergoing application to wait is to use the same thread. The reason your Timer didn't work is because it creates another thread to run itself.

Another option is to use a SwingWorker:
import java.awt.*;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.beans.*;
import javax.swing.*;
public class CountdownPresenterPanel2 {
public static void main(String[] args) {
final JFrame frame = new JFrame();
final JDialog splashScreen = new JDialog(null, Dialog.ModalityType.DOCUMENT_MODAL);
final JLabel textLabel = new JLabel(String.valueOf(5), SwingConstants.CENTER);
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
textLabel.setFont(new Font("Arial", Font.BOLD, 55));
textLabel.setVerticalAlignment(SwingConstants.CENTER);
splashScreen.setUndecorated(true);
splashScreen.getContentPane().add(textLabel);
splashScreen.setSize(600, 300);
splashScreen.setLocationRelativeTo(null);
splashScreen.setVisible(true);
}
});
(new GamePanelInitTask() {
#Override protected void process(java.util.List<Integer> chunks) {
for (Integer value : chunks) {
textLabel.setText(value.toString());
}
}
#Override public void done() {
splashScreen.dispose();
try {
MainGamePanel p = get();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(p);
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
}
}).execute();
}
}
class GamePanelInitTask extends SwingWorker<MainGamePanel, Integer> {
#Override public MainGamePanel doInBackground() {
int currentNumber = 5;
MainGamePanel game = new MainGamePanel();
while (currentNumber > 0 && !isCancelled()) {
currentNumber--;
publish(currentNumber);
game.add(new JLabel(String.format("Label: %d", currentNumber)));
try {
Thread.sleep(1000); //dummy task
} catch (InterruptedException ie) {
ie.printStackTrace();
return null;
}
}
return game;
}
}
class MainGamePanel extends JPanel {
public MainGamePanel() {
super();
try {
Thread.sleep(1000); //dummy task
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Related

Why is my looping GUI timer not showing up?

I'm trying to make a GUI timer without using javax.swing.Timer(kind of a strange task), but I am having trouble making it work. It's supposed to sleep the thread for 1 second, add 1 to seconds, and repeat(infinitely). When I run my program, the icon shows up, but the window does not appear. I'm guessing my error is in the Thread.sleep(1000); line or in that area, but I'm not sure why it doesn't work. Is Thread.sleep(millis)not compatible with swing applications? Do I have to multithread? Here's my program:
import java.awt.*;
import javax.swing.*;
public class GUITimer extends JFrame {
private static final long serialVersionUID = 1L;
private int seconds = 0;
public GUITimer() {
initGUI();
pack();
setVisible(true);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void initGUI(){
JLabel title = new JLabel("Timer");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
title.setFont(titleFont);
title.setHorizontalAlignment(JLabel.CENTER);
title.setBackground(Color.BLACK);
title.setForeground(Color.WHITE);
title.setOpaque(true);
add(title, BorderLayout.NORTH);
JLabel timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
add(timeDisplay, BorderLayout.CENTER);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
seconds++;
initGUI();
}
public static void main(String[] args) {
try {
String className = UIManager.getCrossPlatformLookAndFeelClassName();
UIManager.setLookAndFeel(className);
}
catch (Exception e) {}
EventQueue.invokeLater(new Runnable() {
public void run() {
new GUITimer();
}
});
}
}
EDIT:
I noticed when I print seconds in my method initGUI() to console, it prints them incrementally by one second correctly. So when it looks like:
private void initGUI() {
System.out.println(seconds);
//...
it prints the value of seconds after every second(How the JLabel should). This shows that my loop is working fine, and my Thread.sleep(1000) is OK also. My only problem now, is that the frame is not showing up.
Your main window does not appear, because you called infinite recursion inside constructor. GUITimer will not be created and this lock main thread.
You need use multithreading for this aim. Main thread for drawing time, second thread increment and put value to label
For example:
import javax.swing.*;
import java.awt.*;
public class GUITimer extends JFrame
{
private static final long serialVersionUID = 1L;
private int seconds = 0;
private Thread timerThread;
private JLabel timeDisplay;
public GUITimer()
{
initGUI();
pack();
setVisible(true);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void initGUI()
{
JLabel title = new JLabel("Timer");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
title.setFont(titleFont);
title.setHorizontalAlignment(JLabel.CENTER);
title.setBackground(Color.BLACK);
title.setForeground(Color.WHITE);
title.setOpaque(true);
add(title, BorderLayout.NORTH);
timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
add(timeDisplay, BorderLayout.CENTER);
}
public void start()
{
seconds = 0;
timerThread = new Thread(new Runnable()
{
#Override
public void run()
{
while(true)
{
timeDisplay.setText(Integer.toString(seconds++));
try
{
Thread.sleep(1000L);
}
catch(InterruptedException e) {}
}
}
});
timerThread.start();
}
public void stop()
{
timerThread.interrupt();
}
public static void main(String[] args)
{
try
{
GUITimer timer = new GUITimer();
timer.start();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
The core issue is, you're blocking the UI by continuously calling initGUI, which will eventually fail with a StackOverFlowException, as the method calls never end
The preference would be to use a Swing Timer, but since you've stated you don't want to do that, a better solution would be to use a SwingWorker, the reason for this - Swing is NOT thread safe and SwingWorker provides a convenient mechanism for allowing us to update the UI safely.
Because both Swing Timer and Thead.sleep only guarantee a minimum delay, they are not a reliable means for measuring the passage of time, it would be better to make use of Java 8's Date/Time API instead
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label = new JLabel("00:00:00");
private TimeWorker timeWorker;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(label, gbc);
JButton button = new JButton("Start");
add(button, gbc);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeWorker == null) {
timeWorker = new TimeWorker(label);
timeWorker.execute();
button.setText("Stop");
} else {
timeWorker.cancel(true);
timeWorker = null;
button.setText("Start");
}
}
});
}
}
public class TimeWorker extends SwingWorker<Void, Duration> {
private JLabel label;
public TimeWorker(JLabel label) {
this.label = label;
}
#Override
protected Void doInBackground() throws Exception {
LocalDateTime startTime = LocalDateTime.now();
Duration totalDuration = Duration.ZERO;
while (!isCancelled()) {
LocalDateTime now = LocalDateTime.now();
Duration tickDuration = Duration.between(startTime, now);
publish(tickDuration);
Thread.sleep(500);
}
return null;
}
#Override
protected void process(List<Duration> chunks) {
Duration duration = chunks.get(chunks.size() - 1);
String text = format(duration);
label.setText(text);
}
public String format(Duration duration) {
long hours = duration.toHours();
duration = duration.minusHours(hours);
long minutes = duration.toMinutes();
duration = duration.minusMinutes(minutes);
long millis = duration.toMillis();
long seconds = (long)(millis / 1000.0);
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
}
}

How to create a instance

I am having two classes, a main class and class which extends JPanel and implements Runnable. I am trying to create two threads for the same instance of the JPanel class in an actionListener, but i just don't know where to create the JPanel1 object...
//Edit: Button1 is the start of the application .After that , button 2 will appear with a quick animation of labels and when clicked it(button2) will start the same animation too. How can i do whenever one of these buttons is clicked to launch the run method ?
public void run() {
if(isTom){
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
panel.removeAll();
panel.add(tomLabel1);
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
panel.add(tomLabel2);
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
panel.add(tomLabel3);
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
panel.add(tomLabel4);
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
panel.add(tomLabel5);
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
panel.removeAll();
repaint();
revalidate();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
public Game(){
JFrame frame = new JFrame();
Panel1 key = new Panel1();
key.addKeyListener(key);
frame.add(key);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(true);
frame.setVisible(true);
}
public static void main(String[] args) {
new Game();
}
public class Panel1 extends JPanel implements KeyListener,Runnable{
JButton button1 = new JButton("BUTTON1");
JButton button2 = new JButton("BUTTON2");
add(button1);add(button2);
Thread t = new Thread(this); // This works, but i need it inside the actionListener.
button1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("button1");
Thread x = new Thread(j);//'j' is an JPanel1 object. I need something like this i guess
x.setName("Thread x");});
button2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("button2");
Thread y = new Thread(j);
y.setName("Thread y");
});
public void run(){
System.out.println(Thread.currentThread().getName());
}
First, Swing is NOT thread safe! This means that you should NEVER create or modify the UI from outside of context of the Event Dispatching Thread!
Second, Swing is a single threaded environment, this means that you should never block or execute long running code from within the context of the Event Dispatching Thread, this will cause the UI to freeze until the block is removed.
Your concept is correct, you implementation is wrong, you should use a Swing Timer instead.
Instead of removing and adding labels, use a single label and change it's properties (text/icon, what ever)
See Concurrency in Swing and How to use Swing Timers for more details
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private JButton button1;
private JButton button2;
private SplashScreen splashScreen;
public TestPane() throws IOException {
button1 = new JButton("Button One");
button2 = new JButton("Button Two");
JPanel buttons = new JPanel();
buttons.add(button1);
buttons.add(button2);
button1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
splashScreen.run();
}
});
button2.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
splashScreen.run();
}
});
splashScreen = new SplashScreen();
setLayout(new BorderLayout());
add(splashScreen);
add(buttons, BorderLayout.SOUTH);
}
}
public static class SplashScreen extends JPanel {
protected static final int IMAGE_COUNT = 4;
private JLabel label;
private Timer timer;
private int delta;
private int count;
private Icon[] icons;
private Dimension preferredSize;
public SplashScreen() throws IOException {
String path = "/images/splash";
String ext = ".png";
icons = new Icon[IMAGE_COUNT];
int maxWidth = 0;
int maxHeight = 0;
for (int index = 0; index < IMAGE_COUNT; index++) {
String name = path + (index + 1) + ext;
System.out.println(name);
icons[index] = new ImageIcon(ImageIO.read(getClass().getResource(name)));
maxWidth = Math.max(maxWidth, icons[index].getIconWidth());
maxHeight = Math.max(maxHeight, icons[index].getIconHeight());
}
preferredSize = new Dimension(maxWidth, maxHeight);
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (count >= IMAGE_COUNT) {
count = IMAGE_COUNT - 2;
delta = -1;
} else if (count < 0) {
((Timer)e.getSource()).stop();
} else {
label.setIcon(icons[count]);
count += delta;
}
}
});
setLayout(new BorderLayout());
label = new JLabel();
add(label);
}
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
public void run() {
if (!timer.isRunning()) {
delta = 1;
count = 0;
timer.start();
}
}
}
}
Create a new instance of a Thread with your Panel1 class instance:
button1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("button1");
Thread x = new Thread(Panel1.this);
x.start();
x.setName("Thread x");});
Repeat for the other button with a new Thread object:
button2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("button2");
Thread y = new Thread(Panel1.this);
y.start();
y.setName("Thread y"); });
Panel1.this is referring to the instance of the Panel1 class that is currently running, making sure that your Threads are executing run() of that instance.

How to open a new window or switch using a button?

I'm trying to figure out this for ages, starting to wonder if it is possible!
I have a starting window for my app - I need it so that when I click on a button I have created, the window either closes and opens a new window or the window resizes and leaves just the canvas (ready to put new widgets, sprites etc... ).
I know I need a handler event for this but I just can't get the code to work.
Im not quite sure whats your question but i coded a example with a JFrame and 3 Buttons.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class OpenWindowAndResizeWindow
{
private JFrame frame;
private JButton btnOpenNewWindow;
private JButton btnResizeWindow;
private JButton btnRemoveAllButtons;
/**
* Launch the application.
*/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
OpenWindowAndResizeWindow window = new OpenWindowAndResizeWindow();
window.frame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public OpenWindowAndResizeWindow()
{
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize()
{
frame = new JFrame();
frame.setBounds(100, 100, 300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(getBtnOpenNewWindow(), BorderLayout.NORTH);
frame.getContentPane().add(getBtnResizeWindow(), BorderLayout.SOUTH);
frame.getContentPane().add(getBtnRemoveAllButtons(), BorderLayout.CENTER);
frame.setVisible(true);
}
private void openNewWindow()
{
OpenWindowAndResizeWindow newWindow = new OpenWindowAndResizeWindow();
frame.dispose();
}
private void removeButtons()
{
getBtnOpenNewWindow().setVisible(false);
getBtnRemoveAllButtons().setVisible(false);
getBtnResizeWindow().setVisible(false);
}
private void resizeWindow()
{
Rectangle rectangle = frame.getBounds();
rectangle.width = (int)rectangle.getWidth() + 100;
rectangle.height = (int)rectangle.getHeight() + 100;
frame.setBounds(rectangle);
}
private JButton getBtnOpenNewWindow() {
if (btnOpenNewWindow == null) {
btnOpenNewWindow = new JButton("Open new Window");
btnOpenNewWindow.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
openNewWindow();
}
});
}
return btnOpenNewWindow;
}
private JButton getBtnResizeWindow() {
if (btnResizeWindow == null) {
btnResizeWindow = new JButton("Resize Window");
btnResizeWindow.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
resizeWindow();
}
});
}
return btnResizeWindow;
}
private JButton getBtnRemoveAllButtons() {
if (btnRemoveAllButtons == null) {
btnRemoveAllButtons = new JButton("Remove All Buttons");
btnRemoveAllButtons.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeButtons();
}
});
}
return btnRemoveAllButtons;
}
}
This code is ready to compile with javac or just paste it in your IDE.
Maybe this helps a bit. The Java SE API Documentation is useful too.

Button ActionListener

Ok, so I made a simple program that adds the value to counter each time a button is clicked.
Now, I would like to add "Auto" button feature to increase the value of the counter when the "Auto" button is clicked. I'm having problems with it because it won't render each counter value on the screen, instead the value updates when the loop is done.. Here is my code:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Gui extends JFrame{
private static final long serialVersionUID = 1L;
private JButton uselesButton;
private JButton autoButton;
private FlowLayout layout;
private long counter = 0;
public Gui() {
super("Button");
layout = new FlowLayout(FlowLayout.CENTER);
this.setLayout(layout);
uselesButton = new JButton(String.format("Pressed %d times", counter));
add(uselesButton);
uselesButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
counter++;
uselesButton.setText(String.format("Pressed %d times", counter));
}
});
autoButton = new JButton("Auto");
add(autoButton);
autoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for(long i =0; i < 99999999;i++) {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e1) {
System.out.println("ERROR");
}
counter = i;
uselesButton.setText(String.format("Pressed %d times", counter));
}
}
});
}
}
Keep in mind that I'm a beginner... All help appreciated :)
Take a look at the tutorial about How to Use Swing Timer and then look at my solution:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Gui extends JFrame {
private static final long serialVersionUID = 1L;
private JButton uselesButton;
private JButton autoButton;
private FlowLayout layout;
private long counter = 0;
private javax.swing.Timer timer;
public Gui() {
super("Button");
layout = new FlowLayout(FlowLayout.CENTER);
setLayout(layout);
setDefaultCloseOperation(3);
setSize(300, 300);
setLocationRelativeTo(null);
//initialing swing timer
timer = new javax.swing.Timer(100, getButtonAction());
autoButton = new JButton("Auto");
add(autoButton);
autoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!timer.isRunning()) {
timer.start();
} else {
timer.stop();
}
}
});
}
private ActionListener getButtonAction() {
ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
autoButton.setText(String.format("Pressed %d times", ++counter));
if (counter > 1000) {
timer.stop();
}
}
};
return action;
}
public static void main(String... args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Gui().setVisible(true);
}
});
}
}
your code block the GUI thread (EDT) when enter inside this loop (GUI will hang, the button will not update until you finish), so you should add your code inside another worker thread:
autoButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
#Override
public void run() {
for(long i =0; i < 99999999;i++) {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e1) {
System.out.println("ERROR");
}
counter = i;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
uselesButton.setText(String.format("Pressed %d times", counter));
}
});
}
}
}).start();
}
});
the problem here is that the system is in the loop, so it can't paint the changes.
in order to do that you need to open a new thread. the new thread will do the loop, and the main thread will repaint the form.
one more thing, you shouldn't do sleep on the main thread. you can use a timer that will tick every 10 millisecond instead of sleep(10)
here is an example

Problems with a SwingWorker

I'm trying to get a SwingWorker to work.
I've the following code at the moment:
public class ImageWorker extends SwingWorker<Void, Void> implements KeyListener
{
private JLabel imageLabel;
private ImageIcon basicImage;
private ImageIcon whiteImage;
public static void main(String[] args)
{
new ImageWorker();
}
public ImageWorker()
{
final JFrame frame = new JFrame();
imageLabel = new JLabel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.getContentPane().add(imageLabel);
frame.setVisible(true);
try
{
basicImage = new ImageIcon(ImageIO.read(new File("src\\img\\basis1.jpg")).getScaledInstance(1024, 768, Image.SCALE_SMOOTH));
whiteImage = new ImageIcon(ImageIO.read(new File("src\\img\\wit.jpg")).getScaledInstance(1024, 768, Image.SCALE_SMOOTH));
}
catch(IOException ex)
{
ex.getMessage();
}
this.execute();
}
#Override
protected Void doInBackground()
{
try
{
while (true)
{
displayImage(basicImage);
Thread.sleep(1000L);
if(isCancelled())
return null;
}
}
catch(InterruptedException e)
{
e.getMessage();
}
return null;
}
private void displayImage(final Icon image)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
imageLabel.setIcon(image);
}
});
}
I was expecting the images to appear in the JLabel, but I only see the JFrame popping up. The files are loaded correctly Ive tested that in another setup.
Any pointers?
Here is an example using a Timer rather than using the SwingWorker which really isn't appropriate to your situation. Note that it's not too different from your existing code.
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class ImageWorker implements KeyListener
{
private JLabel imageLabel;
private ImageIcon basicImage;
private ImageIcon whiteImage;
private boolean isBasic = true;
private int delay = 1000; //milliseconds
private Timer timer;
public static void main(String[] args)
{
new ImageWorker();
}
public ImageWorker()
{
final JFrame frame = new JFrame();
imageLabel = new JLabel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.getContentPane().add(imageLabel);
frame.setVisible(true);
try
{
basicImage = new ImageIcon(ImageIO.read(new File("src\\img\\basis1.jpg")).getScaledInstance(1024, 768, Image.SCALE_SMOOTH));
whiteImage = new ImageIcon(ImageIO.read(new File("src\\img\\wit.jpg")).getScaledInstance(1024, 768, Image.SCALE_SMOOTH));
}
catch (IOException ex)
{
ex.getMessage();
ex.printStackTrace();
}
frame.addKeyListener(this);
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(isBasic) {
//display basic image
imageLabel.setIcon(basicImage);
}
else {
//display white image
imageLabel.setIcon(whiteImage);
}
//toggle the flag
isBasic = !isBasic;
}
};
//use a timer instead of SwingWorker
timer = new Timer(delay, taskPerformer);
timer.start();
}
#Override
public void keyPressed(KeyEvent e)
{
//key pressed, we want to stop toggling so stop the timer
timer.stop();
//do whatever else you were doing to set the value for isCancelled();
}
#Override
public void keyReleased(KeyEvent e)
{
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e)
{
// TODO Auto-generated method stub
}
}
A SwingWorker is not appropriate for your situation look into a Timer in the swing package. Here is a link to the API: http://download.oracle.com/javase/6/docs/api/javax/swing/Timer.html
You have the timer run and change an image every second since that is what you need.
Also, whenever you have exceptions, print out a stacktrace or the message at least. Otherwise you won't know if an exception occurs and is caught.

Categories