I have a Swing JFrame. If I create a new JFrame in a new thread during the program execution where will be the EDT ? In the current thread of the last JFrame window or in the first window.
EDIT:
Thanks for your answers.
I understand them and i'm ok with them. I know that we don't must create swing object elsewhere that the EDT but I meet problem.
I explain; I developed a JAVA application for create and extract archive like winrar. You can create several arhive in same time with multi-thread. And recently, I wanted add an information status during the archive creation in the form of JprogressBar in a new JFrame at every creation. But my problem is for generate a communication in the new status frame and the thread who create archive. That's why, I create the JFrame in the archive thread for update the progress bar currently.
But like i could read it in divers information source and on your answers/comments, it's against java swing and performance; I can't create swing object elsewhere that the EDT.
But then, how should I solve my problem ?
The EDT - the event dispatch thread - is separate from any concrete GUI component, such as a JFrame.
Generally you should create all GUI components on the EDT, but that does not mean they own the EDT, nor does the EDT own the components.
To create two JFrames, both on the EDT, you can do the following:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame1 = new JFrame("Frame 1");
frame1.getContentPane().add(new JLabel("Hello in frame 1"));
frame1.pack();
frame1.setLocation(100, 100);
frame1.setVisible(true);
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame2 = new JFrame("Frame 2");
frame2.getContentPane().add(new JLabel("Hello in frame 2"));
frame2.pack();
frame2.setLocation(200, 200);
frame2.setVisible(true);
}
});
}
The Event Dispatch Thread is fixed. It doesn't get reassigned just because you created a Swing object on another thread (which you should never do anyway).
that should be very simple, if all events all done in current EDT, then EDT doesn't exists, another Queue is possible to stating,
from Swing's Listeners,
by caling code wrapped into invokeLater / invokeAndWait,
but safiest (and best for me) would be javax.swing.Action
output
run:
Time at : 19:35:21
There isn't Live EventQueue.isDispatchThread, why any reason for that
There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that
Time at : 19:35:21
Calling from EventQueue.isDispatchThread
Calling from SwingUtilities.isEventDispatchThread
Time at : 19:35:21
Calling from EventQueue.isDispatchThread
Calling from SwingUtilities.isEventDispatchThread
Time at : 19:35:51
There isn't Live EventQueue.isDispatchThread, why any reason for that
There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that
Time at : 19:36:21
There isn't Live EventQueue.isDispatchThread, why any reason for that
There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that
Time at : 19:36:51
There isn't Live EventQueue.isDispatchThread, why any reason for that
There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that
Time at : 19:37:21
There isn't Live EventQueue.isDispatchThread, why any reason for that
There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that
BUILD SUCCESSFUL (total time: 2 minutes 17 seconds)
from code:
import java.awt.EventQueue;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
import javax.swing.*;
public class IsThereEDT {
private ScheduledExecutorService scheduler;
private AccurateScheduledRunnable periodic;
private ScheduledFuture<?> periodicMonitor;
private int taskPeriod = 30;
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
private Date dateRun;
public IsThereEDT() {
scheduler = Executors.newSingleThreadScheduledExecutor();
periodic = new AccurateScheduledRunnable() {
private final int ALLOWED_TARDINESS = 200;
private int countRun = 0;
private int countCalled = 0;
#Override
public void run() {
countCalled++;
if (this.getExecutionTime() < ALLOWED_TARDINESS) {
countRun++;
isThereReallyEDT(); // non on EDT
}
}
};
periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.SECONDS);
periodic.setThreadMonitor(periodicMonitor);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
isThereReallyEDT();
JFrame frame1 = new JFrame("Frame 1");
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame1.getContentPane().add(new JLabel("Hello in frame 1"));
frame1.pack();
frame1.setLocation(100, 100);
frame1.setVisible(true);
}
});
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame2 = new JFrame("Frame 2");
frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame2.getContentPane().add(new JLabel("Hello in frame 2"));
frame2.pack();
frame2.setLocation(200, 200);
frame2.setVisible(true);
isThereReallyEDT();
}
});
}
private void isThereReallyEDT() {
dateRun = new java.util.Date();
System.out.println(" Time at : " + sdf.format(dateRun));
if (EventQueue.isDispatchThread()) {
System.out.println("Calling from EventQueue.isDispatchThread");
} else {
System.out.println("There isn't Live EventQueue.isDispatchThread, why any reason for that ");
}
if (SwingUtilities.isEventDispatchThread()) {
System.out.println("Calling from SwingUtilities.isEventDispatchThread");
} else {
System.out.println("There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that ");
}
System.out.println();
}
public static void main(String[] args) {
IsThereEDT isdt = new IsThereEDT();
}
}
abstract class AccurateScheduledRunnable implements Runnable {
private ScheduledFuture<?> thisThreadsMonitor;
public void setThreadMonitor(ScheduledFuture<?> monitor) {
this.thisThreadsMonitor = monitor;
}
protected long getExecutionTime() {
long delay = -1 * thisThreadsMonitor.getDelay(TimeUnit.MILLISECONDS);
return delay;
}
}
Related
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;
}
}
}
I'm trying to make a clock using as few resources as possible and just relying on my (limited) knowledge of Java. I've come to a road block however. The clock I wrote works, except rather than the text in the jlabel being replaced, it overlaps itself. I've tried fixing this by clearing the value of timeStamp, but it doesn't seem to be working.
public class Clock extends JFrame{
public static void main (String args[]) {
Clock gui = new Clock();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setSize(50,50);
gui.setVisible(true);
gui.setTitle("Clock");
int a = 1;
while (a == 1){
String timeStamp = new SimpleDateFormat("hh:mm:ss a").format(Calendar.getInstance().getTime());
JLabel label;
label = new JLabel();
label.setText(String.valueOf(timeStamp));
timeStamp = "";
gui.add(label);
label.revalidate();
}
}
}
You should not be creating a new JLabel every iteration.
JLabel label = new JLabel();
gui.add(label);
while (a == 1){
String timeStamp = new SimpleDateFormat("hh:mm:ss a").format(Calendar.getInstance().getTime());
label.setText(String.valueOf(timeStamp));
timeStamp = "";
label.revalidate();
}
You should use a SwingWorker to update the clock. Currently you're doing it on the event dispatch thread and thus interfere with the UI rendering.
Besides that you should reuse the label instead of creating a new one for each timestamp. Currently you're stacking labels on top of each other since gui.add() will just add the new label and won't remove the old ones.
Why are you creating a new JLabel in every iteration of the loop?
Don't do that.
Just create a single label in Clock's constructor.
Also, changing the label's text should be done on the event thread, not the main thread.
While the code you wrote "works", you're missing some things to make a stable Swing GUI.
You must always start a Swing application using the SwingUtilities invokelater method. This puts the creation and the execution of the Swing components on the Event Dispatch thread (EDT).
I separated the creation of the GUI from the execution of the GUI. Separation of concerns makes coding each part easier.
In the Timer Runnable, I again use the SwingUtilities invokeLater method to make sure that the updating of the JTextField with the time happens on the EDT.
I stop the Thread before I exit. Generally, you should stop any threads you start, and not rely on the JVM to clean up for you.
Here's the clock.
And here's the code.
package com.ggl.testing;
import java.awt.Color;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class SimpleClock implements Runnable {
private JFrame frame;
private JPanel panel;
private JTextField clockDisplay;
private Timer timer;
#Override
public void run() {
frame = new JFrame("Clock");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 6));
clockDisplay = new JTextField(12);
clockDisplay.setEditable(false);
clockDisplay.setHorizontalAlignment(JTextField.CENTER);
panel.add(clockDisplay);
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(this);
new Thread(timer).start();
}
public void exitProcedure() {
timer.setRunning(false);
frame.dispose();
System.exit(0);
}
public void setText(String text) {
clockDisplay.setText(text);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Clock());
}
public class Timer implements Runnable {
private volatile boolean running;
private SimpleClock clock;
private SimpleDateFormat timeFormat;
public Timer(SimpleClock clock) {
this.clock = clock;
this.running = true;
this.timeFormat = new SimpleDateFormat("h:mm:ss a");
}
#Override
public void run() {
while (running) {
displayTime();
sleep();
}
}
public void displayTime() {
final Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
final String s = timeFormat.format(date);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
clock.setText(s);
}
});
}
public void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
}
When I start my application it opens a JFrame (the main window) and a JFilechooser to select an input directory, which is then scanned.
The scan method itself creates a new JFrame which contains a JButton and a JProgressBar and starts a new Thread which scans the selected Directory. Up until this point everything works fine.
Now I change the Directory Path in my Main Window, which calls the scan method again. This time it creates another JFrame which should contain the JProgressBar and the JButton but it shows up empty (The JFrame Title is still set).
update:
minimal example
public class MainWindow
{
private JFrame _frame;
private JTextArea _textArea;
private ProgressBar _progress;
public MainWindow() throws InterruptedException, ExecutionException
{
_frame = new JFrame("Main Window");
_textArea = new JTextArea();
_frame.add(_textArea);
_frame.setSize(200, 200);
_frame.setVisible(true);
_textArea.setText(doStuffinBackground());
_progress.dispose();
}
private String doStuffinBackground() throws InterruptedException,
ExecutionException
{
setUpProgressBar();
ScanWorker scanWorker = new ScanWorker();
scanWorker.execute();
return scanWorker.get();
}
private void setUpProgressBar()
{
// Display progress bar
_progress = new ProgressBar();
}
class ProgressBar extends JFrame
{
public ProgressBar()
{
super();
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
add(progressBar);
setTitle("Progress Window");
setSize(200, 200);
toFront();
setVisible(true);
}
}
class ScanWorker extends SwingWorker<String, Void>
{
#Override
public String doInBackground() throws InterruptedException
{
int j = 0;
for (int i = 0; i < 10; i++)
{
Thread.sleep(1000);
j += 1;
}
return String.valueOf(j);
}
}
public static void main(String[] args) throws InvocationTargetException,
InterruptedException
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
// Start the main controller
try
{
new MainWindow();
}
catch (InterruptedException | ExecutionException e) {}
}
});
}
}
From the basic looks of your scan method, you are blocking the Event Dispatching Thread, when you scan the directory, which is preventing it from updating the UI.
Specifically, you don't seem to truly understand what Callable and FutureTask are actually used for or how to use them properly...
Calling FutureTask#run will call the Callable's call method...from within the current thread context.
Take a look at Concurrency in Swing for more details...
Instead of trying to use FutureTask and Callable in this manner, consider using a SwingWorker, which is designed to do this kind of work (and uses Callable and FutureTask internally)
Have a look at Worker Threads and SwingWorker for more details
Now, before you jump down my throat and tell me that "it works the first time I ran it", that's because you're not starting your UI properly. All Swing UI's should be create and manipulated from within the context of the Event Dispatching Thread. You main method is executed in, what is commonly called, the "main thread", which is not the same as the EDT. This is basically setting up fluke situation in where the first time you call scan, you are not running within the context of the EDT, allowing it to work ... and breaking the single thread rules of Swing in the process...
Take a look at Initial Threads for more details...
I would also consider using a JDialog instead of another frame, even if it's not modal, it makes for a better paradigm for your application, as it really should only have a single main frame.
Updated based on new code
So, basically, return scanWorker.get(); is a blocking call. It will wait until the doInBackground method completes, which means it's block the EDT, still...'
Instead, you should be making use of the publish, process and/or done methods of the SwingWorker
import java.util.ArrayList;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MainWindow {
private JFrame _frame;
private JTextArea _textArea;
private ProgressBar _progress;
public MainWindow() {
_frame = new JFrame("Main Window");
_textArea = new JTextArea();
_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
_frame.add(new JScrollPane(_textArea));
_frame.setSize(200, 200);;
_frame.setVisible(true);
doStuffinBackground();
}
private void doStuffinBackground() {
// _progress = new ProgressBar();
// ScanWorker scanWorker = new ScanWorker();
// scanWorker.execute();
// return scanWorker.get();
_progress = new ProgressBar();
ScanWorker worker = new ScanWorker(_textArea, _progress);
worker.execute();
_progress.setVisible(true);
}
class ProgressBar extends JDialog {
public ProgressBar() {
super(_frame, "Scanning", true);
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
add(progressBar);
setTitle("Progress Window");
pack();
setLocationRelativeTo(_frame);
}
}
class ScanWorker extends SwingWorker<List<String>, String> {
private JTextArea textArea;
private ProgressBar progressBar;
protected ScanWorker(JTextArea _textArea, ProgressBar _progress) {
this.textArea = _textArea;
this.progressBar = _progress;
}
#Override
protected void process(List<String> chunks) {
for (String value : chunks) {
textArea.append(value + "\n");
}
}
#Override
public List<String> doInBackground() throws Exception {
System.out.println("...");
int j = 0;
List<String> results = new ArrayList<>(25);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
j += 1;
System.out.println(j);
results.add(Integer.toString(j));
publish(Integer.toString(j));
}
return results;
}
#Override
protected void done() {
progressBar.dispose();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainWindow();
}
});
}
}
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.
Bellow is the code for the simplest GUI countdown. Can the same be done in a shorter and more elegant way with the usage of the Swing timer?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class CountdownNew {
static JLabel label;
// Method which defines the appearance of the window.
public static void showGUI() {
JFrame frame = new JFrame("Simple Countdown");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("Some Text");
frame.add(label);
frame.pack();
frame.setVisible(true);
}
// Define a new thread in which the countdown is counting down.
public static Thread counter = new Thread() {
public void run() {
for (int i=10; i>0; i=i-1) {
updateGUI(i,label);
try {Thread.sleep(1000);} catch(InterruptedException e) {};
}
}
};
// A method which updates GUI (sets a new value of JLabel).
private static void updateGUI(final int i, final JLabel label) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
label.setText("You have " + i + " seconds.");
}
}
);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
}
Yes you SHOULD use a Swing Timer. You SHOULD NOT, use a util Timer and TimerTask.
When a Swing Timer fires the code is executed on the EDT which means you just need to invoke the label.setText() method.
When using the uitl Timer and TimerTask, the code DOES NOT execute on the EDT, which means you need to wrap your code in a SwingUtilities.invokeLater to make sure the code executes on the EDT.
And that is way using a Swing Timer is shorter and more elegant than your current approach, it simplifies the coding because to code is executed on the EDT.
You could make it a little more elegant by using Timer with an appropriate TimerTask.
Yes, use a timer. updateGUI would be the code for the timer task, but it will need some changes as you won't be able to pass in i for each call since you just get a run() method.