A Swing based Timer is recommended for updating GUI components - because the calls to the components are automatically on the Event Dispatch Thread (the correct thread for updating Swing or AWT based components).
The Swing Timer though, has a tendency to 'drift' off time. If you create a timer that fires every second, after an hour or so, it might have drifted a few seconds above or below the elapsed time.
When using a Swing Timer to update a display which must be accurate (e.g. a countdown timer / stop watch), how do we avoid this time drift?
The trick here is to keep track of the elapsed time, check it frequently, and update the GUI when the actual time required ('one second' in this case) has passed.
Here is an example of doing that. Pay attention to the comments in the code.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class NonDriftingCountdownTimer {
private JComponent ui = null;
private Timer timer;
private JLabel outputLabel;
NonDriftingCountdownTimer() {
initUI();
}
/** Keeps track of the start time and adjusts the count
* based on the ELAPSED time.
* This should be used with a short time between listener calls. */
class TimerActionListener implements ActionListener {
long start = -1l;
int duration;
int count = 0;
TimerActionListener(int duration) {
this.duration = duration;
}
#Override
public void actionPerformed(ActionEvent e) {
long time = System.currentTimeMillis();
if (start<0l) {
start = time;
} else {
long next = start+(count*1000);
if (time>next) {
count++;
outputLabel.setText((duration-count)+"");
if (count==duration) {
timer.stop();
JOptionPane.showMessageDialog(
outputLabel, "Time Is Up!");
}
}
}
}
}
public final void initUI() {
if (ui!=null) return;
ui = new JPanel(new BorderLayout(4,4));
ui.setBorder(new EmptyBorder(4,4,4,4));
JPanel controlPanel = new JPanel();
ui.add(controlPanel, BorderLayout.PAGE_START);
final SpinnerNumberModel durationModel =
new SpinnerNumberModel(10, 1, 1200, 1);
JSpinner spinner = new JSpinner(durationModel);
controlPanel.add(spinner);
JButton startButton = new JButton("Start");
ActionListener startListener = (ActionEvent e) -> {
int duration = durationModel.getNumber().intValue();
TimerActionListener timerActionListener =
new TimerActionListener(duration);
if (timer!=null) { timer.stop(); }
// Note the short time of fire. This will allow accuracy
// to within 1/50th of a second (without gradual drift).
timer = new Timer(20, timerActionListener);
timer.start();
};
startButton.addActionListener(startListener);
controlPanel.add(startButton);
outputLabel = new JLabel("0000", SwingConstants.TRAILING);
outputLabel.setFont(new Font(Font.MONOSPACED, Font.BOLD, 200));
ui.add(outputLabel);
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
NonDriftingCountdownTimer o = new NonDriftingCountdownTimer();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
Related
I have a GUI created in a class called MainFrame. One of the JPanels of the GUI displays the current time and date, by second. When the user decides to use the GUI to analyze data, it invokes a class that processes data. When the data process is happening, the timer pauses, then resumes when the dataprocess is over. How can I have the timer continuously run even if the program is running? The timer is its own thread, but I do not understand where to start a thread for a JPanel.
Here are some code cut-outs
App.java (app to start the entire GUI)
public class App {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainFrame();
}
});
}
}
MainFrame (class that handles the JPanels and dataprocess impl)
public class MainFrame extends JFrame {
private DataProcess dataProcess = null;
...
...
private StatusPanel statusPanel;
...
...
public MainFrame() {
...
setJMenuBar(createFrameMenu());
initializeVariables();
constructLayout();
createFileChooser();
constructAppWindow();
}
private void initializeVariables() {
this.dataProcess = new DataProcess();
...
this.statusPanel = new StatusPanel();
...
}
private void constructLayout() {
JPanel layoutPanel = new JPanel();
layoutPanel.setLayout(new GridLayout(0, 3));
layoutPanel.add(dataControlsPanel());
setLayout(new BorderLayout());
add(layoutPanel, BorderLayout.CENTER);
add(statusPanel, BorderLayout.PAGE_END);
}
StatusPanel (panel that shows timer etc)
public class StatusPanel extends JPanel {
private static final long serialVersionUID = 1L;
private JLabel statusLabel;
private JLabel timeLabel;
private Timer timer;
public StatusPanel() {
initializeVariables();
constructLayout();
startTimer();
}
private void constructLayout() {
setLayout(new FlowLayout(FlowLayout.CENTER));
add(statusLabel);// , FlowLayout.CENTER
add(timeLabel);
}
public void startTimer() {
this.timer.start();
}
public void stopTimer() {
this.timer.setRunning(false);
}
private void initializeVariables() {
this.statusLabel = new JLabel();
this.timeLabel = new JLabel();
this.statusLabel.setText(StringConstants.STATUS_PANEL_TEXT);
this.timer = new Timer(timeLabel);
}
}
Timer.java (timer that is used in StatusPanel)
public class Timer extends Thread {
private boolean isRunning;
private JLabel timeLabel;
private SimpleDateFormat timeFormat;
public Timer(JLabel timeLabel) {
initializeVariables(timeLabel);
}
private void initializeVariables(JLabel timeLabel) {
this.timeLabel = timeLabel;
this.timeFormat = new SimpleDateFormat("HH:mm:ss dd-MM-yyyy");
this.isRunning = true;
}
#Override
public void run() {
while (isRunning) {
Calendar calendar = Calendar.getInstance();
Date currentTime = calendar.getTime();
timeLabel.setText(timeFormat.format(currentTime));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
Data process is done in the dataControlsPanel by use of actionlisteners.
When the user decides to use the GUI to analyze data, it invokes a class that processes data. When the data process is happening, the timer pauses, then resumes when the dataprocess is over. How can I have the timer continuously run even if the program is running
First of all, your timer should be a javax.swing.Timer or "Swing" Timer. This is built to work specifically on the Swing event thread and so should avoid many of the Swing threading problems that your current code shows -- for example, here: timeLabel.setText(timeFormat.format(currentTime)); -- this makes a Swing call from a background thread and is dangerous code. Next
The processing code should go into a SwingWorker. When the worker executes, you can pause the Swing Timer by calling stop() on the Timer, or simply let the timer to continue to run. When the SwingWorker has completed its action -- something I usually listen for with a PropertyChangeListener added to the SwingWorker, listening for its state property to change to SwingWorker.StateValue.DONE, call get() on the worker to extract any data it holds and more importantly to capture any exceptions that might be thrown.
For example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyApp extends JPanel {
// display the date/time
private static final String DATE_FORMAT = "HH:mm:ss dd-MM-yyyy";
private static final DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
// timer updates measures seconds, but updates every 0.2 sec's to be sure
private static final int TIMER_DELAY = 200;
// JLabel that shows the date/time
private JLabel timeLabel = new JLabel("", SwingConstants.CENTER);
// JButton's Action / listener. This starts long-running data processing
private Action dataProcessAction = new DataProcessAction("Process Data");
// the SwingWorker that the above Action executes:
private LongRunningSwProcess longRunningProcess;
// label to display the count coming from the process above
private JLabel countLabel = new JLabel("00");
public MyApp() {
// create a simple GUI
JPanel dataProcessingPanel = new JPanel();
dataProcessingPanel.add(new JButton(dataProcessAction)); // button that starts process
dataProcessingPanel.add(new JLabel("Count:"));
dataProcessingPanel.add(countLabel);
setLayout(new BorderLayout());
add(timeLabel, BorderLayout.PAGE_START);
add(dataProcessingPanel);
showTimeLabelCurrentTime();
// create and start Swing Timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
// display count from swing worker
public void setCount(int newValue) {
countLabel.setText(String.format("%02d", newValue));
}
// clean up code after SwingWorker finishes
public void longRunningProcessDone() {
// re-enable JButton's action
dataProcessAction.setEnabled(true);
if (longRunningProcess != null) {
try {
// handle any exceptions that might get thrown from the SW
longRunningProcess.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
// display the current time in our timeLabel JLabel
private void showTimeLabelCurrentTime() {
long currentTime = System.currentTimeMillis();
Date date = new Date(currentTime);
timeLabel.setText(dateFormat.format(date));
}
// Timer's ActionListener is simple -- display the current time in the timeLabel
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
showTimeLabelCurrentTime();
}
}
// JButton's action. This starts the long-running SwingWorker
private class DataProcessAction extends AbstractAction {
public DataProcessAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false); // first disable the button's action
countLabel.setText("00"); // reset count label
// then create SwingWorker and listen to its changes
longRunningProcess = new LongRunningSwProcess();
longRunningProcess.addPropertyChangeListener(new DataProcessListener());
// execute the swingworker
longRunningProcess.execute();
}
}
// listen for state changes in our SwingWorker
private class DataProcessListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(LongRunningSwProcess.COUNT)) {
setCount((int)evt.getNewValue());
} else if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
longRunningProcessDone();
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("My App");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MyApp());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// mock up of SwingWorker for long-running action
class LongRunningSwProcess extends SwingWorker<Void, Integer> {
public static final String COUNT = "count";
private static final int MIN_TIME_OUT = 5;
private static final int MAX_TIME_OUT = 10;
private int count = 0;
#Override
protected Void doInBackground() throws Exception {
// all this mock up does is increment a count field
// every second until timeOut reached
int timeOut = MIN_TIME_OUT + (int) (Math.random() * (MAX_TIME_OUT - MIN_TIME_OUT));
for (int i = 0; i < timeOut; i++) {
setCount(i);
TimeUnit.SECONDS.sleep(1);
}
return null;
}
// make count a "bounded" property -- one that will notify listeners if changed
public void setCount(int count) {
int oldValue = this.count;
int newValue = count;
this.count = newValue;
firePropertyChange(COUNT, oldValue, newValue);
}
public int getCount() {
return count;
}
}
I made a puzzle game in java Applet, and I need to add a timer that runs for 5 minutes where the player has to solve the puzzle within this time, if not a dialog box will appear asking to retry, so then I need the timer to start again.
Can someone tell me how can I code this.
public void init (){
String MINUTES = getParameter("minutes");
if (MINUTES != null) remaining = Integer.parseInt(MINUTES) * 600000;
else remaining = 600000; // 10 minutes by default
// Create a JLabel to display remaining time, and set some PROPERTIES.
label = new JLabel();
// label.setHorizontalAlignment(SwingConstants.CENTER );
// label.setOpaque(false); // So label draws the background color
// Now add the label to the applet. Like JFrame and JDialog, JApplet
// has a content pane that you add children to
count.add(label);
Puzframe.add(count,BorderLayout.SOUTH);
// Obtain a NumberFormat object to convert NUMBER of minutes and
// seconds to strings. Set it up to produce a leading 0 if necessary
format = NumberFormat.getNumberInstance();
format.setMinimumIntegerDigits(2); // pad with 0 if necessary
// Specify a MouseListener to handle mouse events in the applet.
// Note that the applet implements this interface itself
// Create a timer to call the actionPerformed() method immediately,
// and then every 1000 milliseconds. Note we don't START the timer yet.
timer = new Timer(1000, this);
timer.setInitialDelay(0); //
timer.start(); }
public void start() { resume(); }
//The browser calls this to stop the applet. It may be restarted later.
//The pause() method is defined below
void resume() {
// Restore the time we're counting down from and restart the timer.
lastUpdate = System.currentTimeMillis();
timer.start(); // Start the timer
}`
//Pause the countdown
void updateDisplay() {
long now = System.currentTimeMillis(); // current time in ms
long elapsed = now - lastUpdate; // ms elapsed since last update
remaining -= elapsed; // adjust remaining time
lastUpdate = now; // remember this update time
// Convert remaining milliseconds to mm:ss format and display
if (remaining < 0) remaining = 0;
int minutes = (int)(remaining/60000);
int seconds = (int)((remaining)/1000);
label.setText(format.format(minutes) + ":" + format.format(seconds));
label.setForeground(new Color(251,251,254));
label.setBackground(new Color(0,0,0));
// If we've completed the countdown beep and display new page
if (remaining == 0) {
// Stop updating now.
timer.stop();
}
count.add(label);
Puzframe.add(label,BorderLayout.SOUTH); }
This what I have so far, but my problem is that it doesn't appear in my game. I'm calling the updateDisplay() from actionPerformed
Use Swing Timer it is made for such a scenario
//javax.swing.Timer
timer = new Timer(4000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(mainFrame,
"End Of Game",
"5 minutes has passed",
JOptionPane.ERROR_MESSAGE);
}
});
I prepared a simple example to demonstrate it
Example
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SwingControlDemo {
private JFrame mainFrame;
private JPanel controlPanel;
private Timer timer;
public SwingControlDemo(){
prepareGUI();
}
public static void main(String[] args){
SwingControlDemo swingControlDemo = new SwingControlDemo();
swingControlDemo.showEventDemo();
}
private void prepareGUI(){
mainFrame = new JFrame("Java SWING Examples");
mainFrame.setSize(400,400);
mainFrame.setLayout(new GridLayout(3, 1));
mainFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowEvent){
System.exit(0);
}
});
controlPanel = new JPanel();
controlPanel.setLayout(new FlowLayout());
mainFrame.add(controlPanel);
mainFrame.setVisible(true);
//javax.swing.Timer
timer = new Timer(4000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(mainFrame,
"End Of Game",
"5 minutes has passed",
JOptionPane.ERROR_MESSAGE);
}
});
}
private void showEventDemo(){
JButton okButton = new JButton("Start Game");
okButton.setActionCommand("OK");
okButton.addActionListener(new ButtonClickListener());
controlPanel.add(okButton);
mainFrame.setVisible(true);
}
private class ButtonClickListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
timer.start();
String command = e.getActionCommand();
if( command.equals( "OK" )) {
System.out.println("Timer started");
}
}
}
}
I have a class called Gui. This is where I place all my labels and buttons.
It also contains a button.addactionlistener.
When the button is pressed it starts another thread(stopwatch).
This is when stopwatch enters a loop which keeps updating the ms,sec,min in a while loop.
Stopwatch is another class file. Stopwatch contains the ms,sec,min.
How do I update the gui label with the stopwatch ms,sec,min?
public class Gui {
JFrame swFrame = new JFrame("Stopwatch");
Stopwatch sw = new Stopwatch();
Thread t1 = new Thread(sw);
private JPanel p;
private JButton b1;
private JButton b2;
private JButton b3;
private JLabel l1;
private JLabel l2;
public Gui()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
swFrame.setSize(500,400);
swFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
p = new JPanel();
b1 = new JButton("StartStop");
b2 = new JButton("LapTime");
b3 = new JButton("Reset");
l1 = new JLabel("bla");
l2 = new JLabel("blala");
p.add(b1);
p.add(b2);
p.add(b3);
p.add(l1);
p.add(l2);
swFrame.add(p);
b1.setActionCommand("StartStop");
b2.setActionCommand("LapTime");
b3.setActionCommand("Reset");
b1.addActionListener(new ButtonClickListener());
b2.addActionListener(new ButtonClickListener());
b3.addActionListener(new ButtonClickListener());
}
});
}
private class ButtonClickListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
String command = e.getActionCommand();
if( command.equals( "StartStop" ))
{
if(t1.isAlive())
{
t1.interrupt();
}
else
{
t1.start();
!!!//How to update the jlabel from the moment t1.starts?!!!!
}
}
else if( command.equals( "LapTime" ) )
{
l2.setText("Submit Button clicked.");
}
else if(command.equals("Reset"))
{
}
}
}
class stopwatch
public class Stopwatch implements Runnable
{
private int min;
private int sec;
private long ms;
Timer timerSW = new Timer();
JLabel l1;
public void run()
{
ms = System.currentTimeMillis();
while(!Thread.currentThread().isInterrupted())
{
int seconds = (int) (ms / 1000) % 60 ;
int minutes = (int) ((ms / (1000*60)) % 60);
}
}
I also have a program class which contains a main method. This calls the Gui.
public class Program {
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
Gui gui = new Gui();
gui.swFrame.setVisible(true);
}
});
}
}
How do I update the gui label with the stopwatch ms,sec,min?
Carefully, Swing is not thread safe and you should not modify it's state outside of the context of the Event Dispatching Thread.
One approach would be to use an Observer Pattern, where by your timer triggers updates to which the UI can respond.
A simpler solution might to use a Swing Timer over a Thread, because a Swing Timer is executes its notifications within the context of the EDT
Consider having a look at Concurrency in Swing and How to use Swing Timers for more details
I am creating a simple user interface whereby a user could click on a button to run a specific Java class. Upon clicking, the progress of the task should be displayed to the user and also provide a Cancel button for the user to terminate the task at any point of time while the task is running.
In this case, I am using a ProgressMonitor to be displayed when a user clicks on a JButton in the UI, whereby runEngineerBuild() containing a runnable thread will be invoked to execute the methods of another Java class (called EngineerBuild.java). However, the ProgressMonitor dialog does not display. How can I get the ProgressDialog to show? I'm wondering if it is because of the nature of multiple running threads or maybe I'm missing out on something. Would really appreciate your help!
In SecondPanel.java:
package mainApplication;
import java.awt.Font;
public class SecondPanel extends JPanel {
private MainApplication ma = null; // main JFrame
private JPanel pnlBtn;
private JPanel pnlProgress;
private JButton btnRunAll;
private JButton btnEngBuild;
private JButton btnWholeDoc;
private JButton btnCancelProgress;
private JLabel lblTitleSteps;
private JLabel lblAlt;
private JLabel lbl_1a;
private JLabel lbl_1b_c;
private JLabel lblTitleStatus;
private JProgressBar progressRunAll;
private JProgressBar progressEngBuild;
private JProgressBar progressWholeDoc;
private Property property = Property.getInstance();
// private Task task;
private boolean cancelFlag;
/**
* Create the panel for Step 1 TabbedPane.
*/
public SecondPanel(MainApplication mainApp) {
// TODO Auto-generated constructor stub
super();
ma = mainApp;
}
public SecondPanel() {
this.setBackground(new Color(224, 255, 255));
this.setBounds(0, 0, 745, 1350);
this.setPreferredSize(new Dimension(745, 600));
this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
pnlBtn = new JPanel();
pnlBtn.setLayout(new BoxLayout(pnlBtn, BoxLayout.PAGE_AXIS));
pnlBtn.setAlignmentY(Component.TOP_ALIGNMENT);
pnlProgress = new JPanel();
pnlProgress.setLayout(new BoxLayout(pnlProgress, BoxLayout.PAGE_AXIS));
pnlProgress.setAlignmentY(TOP_ALIGNMENT);
pnlBtn.add(Box.createRigidArea(new Dimension(0, 15)));
btnEngBuild = new JButton("Run EngineerBuild.java");
btnEngBuild.setToolTipText("Build search engineer");
btnEngBuild.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// start activity
activity = new SimulatedActivity(1000);
activity.start();
// launch progress dialog
progressDialog = new ProgressMonitor(ma,
"Waiting for Simulated Activity", null, 0, activity
.getTarget());
progressDialog.setMillisToPopup(1000);
// start timer
activityMonitor = new Timer(500, null);
activityMonitor.start();
btnEngBuild.setEnabled(false);
}
});
activityMonitor = new Timer(500, new ActionListener() {
private PrintStream textArea;
public void actionPerformed(ActionEvent event) {
int current = activity.getCurrent();
// show progress
runEngineerBuild();
textArea.append(current + "\n");
progressDialog.setProgress(current);
// check if task is completed or canceled
if (current == activity.getTarget() || progressDialog.isCanceled()) {
activityMonitor.stop();
progressDialog.close();
activity.interrupt();
btnEngBuild.setEnabled(true);
}
}
});
btnEngBuild.setMinimumSize(new Dimension(200, 30));
btnEngBuild.setPreferredSize(new Dimension(200, 30));
btnEngBuild.setMaximumSize(new Dimension(200, 30));
pnlBtn.add(btnEngBuild);
pnlBtn.add(Box.createRigidArea(new Dimension(0, 15)));
// components in panel progress
lblTitleStatus = new JLabel();
lblTitleStatus.setText("<html><u>Task Status</u></html>");
progressEngBuild = new JProgressBar();
Border border2 = BorderFactory.createTitledBorder("Run EngineerBuild");
progressEngBuild.setBorder(border2);
// title
pnlProgress.add(lblTitleStatus);
pnlProgress.add(Box.createRigidArea(new Dimension(0, 15)));
pnlProgress.add(progressEngBuild);
pnlProgress.add(Box.createRigidArea(new Dimension(0, 15)));
this.add(Box.createRigidArea(new Dimension(15, 10)));
this.add(pnlBtn);
this.add(Box.createRigidArea(new Dimension(50, 10)));
this.add(pnlProgress);
}
public void runEngineerBuild()
{
EngineerBuildRunnable ebr = new EngineerBuildRunnable();
ebr.run();
}
private class EngineerBuildRunnable implements Runnable {
EngineerBuild eb;
public EngineerBuildRunnable() {
eb = new EngineerBuild();
}
public void run() {
eb.initial();
eb.storeIntoFile();
}
}
private Timer activityMonitor;
private ProgressMonitor progressDialog;
private SimulatedActivity activity;
public static final int WIDTH = 300;
public static final int HEIGHT = 200;
}
/**
* A simulated activity thread.
*/
class SimulatedActivity extends Thread {
/**
* Constructs the simulated activity thread object. The thread increments a
* counter from 0 to a given target.
*
* #param t
* the target value of the counter.
*/
public SimulatedActivity(int t) {
current = 0;
target = t;
}
public int getTarget() {
return target;
}
public int getCurrent() {
return current;
}
public void run() {
try {
while (current < target && !interrupted()) {
sleep(100);
current++;
}
} catch (InterruptedException e) {
}
}
private int current;
private int target;
}
Here's the link for the original ProgressMonitor code if you're interested:
User Interface Programming - Example 1-11 ProgressMonitorTest.java
In all likelihood, calling runEngineerBuild() will call long-running code, something that you're doing on the Swing event thread, and this will tie up the thread rendering your GUI useless and frozen until that long-running code has completed its run. The solution is the same as all similar issues -- call runEngineerBuild() in a background thread such as a SwingWorker.
A quick fix would be to explicitly call runEngineerBuild() in a simple thread:
EngineerBuildRunnable ebr = new EngineerBuildRunnable();
new Thread(ebr).start();
// ebr.run(); // !!! don't call a Runnable's run method directly !!!!
For details on how to use a SwingWorker, please check out: Lesson: Concurrency in Swing.
I've got a "status" JLabel in one class (named Welcome) and the timer in another one (named Timer). Right now, the first one displays the word "status" and the second one should be doing the countdown. The way I would like it to be, but don't know how to - display 10, 9, 8, 7 ... 0 (and go to the next window then). My attempts so far:
// class Welcome
setLayout(new BorderLayout());
JPanel area = new JPanel();
JLabel status = new JLabel("status");
area.setBackground(Color.darkGray);
Font font2 = new Font("SansSerif", Font.BOLD, 25);
status.setFont(font2);
status.setForeground(Color.green);
area.add(status, BorderLayout.EAST); // can I put it in the bottom-right corner?
this.add(area);
and the timer:
public class Timer implements Runnable {
// public void runThread() {
// new Thread(this).start();
// }
public void setText(final String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(text); // link to status here I guess
}
});
}
public void run() {
for (int i = 10; i > 0; i--) {
// set the label
final String text = "(" + i + ") seconds left";
setText(text);
// // sleep for 1 second
// try {
// Thread.currentThread();
// Thread.sleep(1000);
// } catch (Exception ex) {
// }
}
// go to the next window
UsedBefore window2 = new UsedBefore();
window2.setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
// runThread();
}
} // end class
I agree that you should consider using a "Java" Timer as per Anh Pham, but in actuality, there are several Timer classes available, and for your purposes a Swing Timer not a java.util.Timer as suggested by Anh would suit your purposes best.
As for your problem, it's really nothing more than a simple problem of references. Give the class with the label a public method, say setCountDownLabelText(String text), and then call that method from the class that holds the timer. You'll need to have a reference of the GUI class with the timer JLabel in the other class.
For example:
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Welcome extends JPanel {
private static final String INTRO = "intro";
private static final String USED_BEFORE = "used before";
private CardLayout cardLayout = new CardLayout();
private JLabel countDownLabel = new JLabel("", SwingConstants.CENTER);
public Welcome() {
JPanel introSouthPanel = new JPanel();
introSouthPanel.add(new JLabel("Status:"));
introSouthPanel.add(countDownLabel);
JPanel introPanel = new JPanel();
introPanel.setPreferredSize(new Dimension(400, 300));
introPanel.setLayout(new BorderLayout());
introPanel.add(new JLabel("WELCOME", SwingConstants.CENTER), BorderLayout.CENTER);
introPanel.add(introSouthPanel, BorderLayout.SOUTH);
JPanel usedBeforePanel = new JPanel(new BorderLayout());
usedBeforePanel.setBackground(Color.pink);
usedBeforePanel.add(new JLabel("Used Before", SwingConstants.CENTER));
setLayout(cardLayout);
add(introPanel, INTRO);
add(usedBeforePanel, USED_BEFORE);
new HurdlerTimer(this).start();
}
private static void createAndShowUI() {
JFrame frame = new JFrame("Welcome");
frame.getContentPane().add(new Welcome());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
public void setCountDownLabelText(String text) {
countDownLabel.setText(text);
}
public void showNextPanel() {
cardLayout.next(this);
}
}
class HurdlerTimer {
private static final int TIMER_PERIOD = 1000;
protected static final int MAX_COUNT = 10;
private Welcome welcome; // holds a reference to the Welcome class
private int count;
public HurdlerTimer(Welcome welcome) {
this.welcome = welcome; // initializes the reference to the Welcome class.
String text = "(" + (MAX_COUNT - count) + ") seconds left";
welcome.setCountDownLabelText(text);
}
public void start() {
new Timer(TIMER_PERIOD, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (count < MAX_COUNT) {
count++;
String text = "(" + (MAX_COUNT - count) + ") seconds left";
welcome.setCountDownLabelText(text); // uses the reference to Welcome
} else {
((Timer) e.getSource()).stop();
welcome.showNextPanel();
}
}
}).start();
}
}
Since you're using Swing you should use the javax.swing.Timer, not the java.util.Timer. You can set the timer to fire at 1 second (1000 ms) intervals and have your listener do the updating. Since Swing updates must take place in the event dispatch thread your listener is the perfect place for status.setText.
there's already a Timer class in java: http://www.exampledepot.com/egs/java.util/ScheduleRepeat.html
Why not put the setText method in the welcome class and just do 'status.setText(text)'?
And you might try BorderLayout.SOUTH or .PAGE END or .LINE END to get the timer in the lower right corner