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
Related
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);
}
}
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 am trying to build a simple GUI clock with multithreading.My purpose is making two identical exampl clock window.
public class JavaApplication9 {
public static void main(String[] args) {
JFrame clock = new TextClockWindow();
clock.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
clock.setVisible(true);
}//end main}
class TextClockWindow extends JFrame {
private JTextField timeField; // set by timer listener
private JButton listener;
public TextClockWindow() {
// GUI
timeField = new JTextField(6);
timeField.setFont(new Font("sansserif", Font.PLAIN, 48));
JButton button1 = new JButton("Action");
add(button1);
button1.addActionListener((ActionListener) listener);
ActionListener listener=new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){setBackground(Color.red );
}
};
Container content = this.getContentPane();
content.setLayout(new FlowLayout());
content.add(timeField);
this.setTitle("My_simple_clock"); this.pack();
// Create a 1-second timer and action listener for it.
// Specify package because there are two Timer classes
javax.swing.Timer t = new javax.swing.Timer(1000,
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Calendar now = Calendar.getInstance();
int h = now.get(Calendar.HOUR_OF_DAY);
int m = now.get(Calendar.MINUTE);
int s = now.get(Calendar.SECOND);
timeField.setText("" + h + ":" + m + ":" + s);
}
});
t.start();
}
This is code without multithreading.But wheni trying to use Runnable some error occured.
In method main Non Static variable cannot be referenced in a static context.
My code with multithreading:
public class MyClock{
public static void main(String[] args) {
Runnable r=new Clocks();
Thread n=new Thread(r);
n.start();
}
public class Clocks implements Runnable {
public Clocks() {}
public void run() {JFrame clock = new TextClockWindow();
clock.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
clock.setVisible(true);
}
please help find the reason why it is not work.runnable is writing correctly....
Is there a reason why you declared Clocks class as inner class ?
Move your Clocks class outside MyClock class and remove the public qualifier if you're declaring the class in the same file. It will start working.
I need a timer implemented in my application, which will do a countdown from 10 sec - 0 sec.
and, display the countdown in a JLabel.
Here's my implementation;
...
Timer t = new Timer(1000, new List());
t.start();
}
class List implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
int sec = 0;
label.setText(""+sec);
// Do a if- condition check to see if the clock has reached to, and then stop
}
}
I was expecting the JLabel to start counting from 0 - 10 and then stop. But it doesn't. The JLabel set the value 0 and it doesn't get incremented.
UPDATE 1
t = new Timer(1000, new Listner());
t.start();
}
class Listner implements ActionListener{
private int counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
lable.setText(""+ (counter++));
if (counter == 10)
t.removeActionListener(this);
}
}
You are not storing nor incrementing secs anywhere so I don't see how it should get updated, try with
Timer timer;
void start() {
timer = new Timer(1000,new List());
}
class List implements ActionListener {
private counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
label.setText(""+counter++);
if (counter == 10)
timer.removeActionListener(this);
}
}
Mind that you need to store a reference to the timer somewhere to be able to remove the listener from it once countdown finished.
Well each time the timer is called it declares the int variable sec to 0. Hence the Label doesnt get updated.
You should declare the sec variable as a global variable and then in the actionPerformed method increment its value each time it is called.
public int sec = 0;
class List implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
sec++;
label.setText(""+sec);
// Do a if- condition check to see if the clock has reached to, and then stop
}
}
A complete example
public class ATimerExample {
Timer timer;
int counter = 0;
public ATimerExample() {
final JFrame frame = new JFrame("somethgi");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JLabel label = new JLabel("0");
JPanel panel = new JPanel();
panel.add(label, BorderLayout.SOUTH);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
label.setText(String.valueOf(counter));
counter++;
if (counter == 10) {
//timer.removeActionListener(this);
timer.stop();
}
}
});
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ATimerExample();
}
});
}
}
Since java reads the time in milliseconds, it should be 10000 instead of 1000. try your code and see if that works. I had the same problem when I wanted 30 seconds. And instead of writing Timer t = new Timer(30000, new List()); t.start();
I wrote Timer t = new Timer(3000, new List());
t.start();
That made my program to stop every after 3 seconds. I would suggest, you use 10000 instead of 1000.
Remember to do: t.stop() in your List class. Thanks
I have hit another wall. After getting my key input working, I have been racking my brains for hours, i want to create a pause function, so that if the same key is pressed again the timertask stops running (i.e the game is paused)
JPanel component = (JPanel)frame.getContentPane();
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
component.getActionMap().put("space", (new AbstractAction(){
public void actionPerformed(ActionEvent e){
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask(){
public void run(){
grid.stepGame();
}
},250, 250);
}}));
}
The problem is i cant use a global boolean isRunning var and switch it each time the key is pressed because the timerTask method in a nested class (so the boolean isRunning would have to be declared final to be accessed...). Any ideas on how to detect if the key is pressed again or if the game is already running so i can pause/cancel my timerTask.
Many Thanks Sam
Since this is a Swing game, you should be using a javax.swing.Timer or Swing Timer and not a java.util.Timer. By using a Swing Timer, you guarantee that the code being called intermittently is called on the EDT, a key issue for Swing apps, and it also has a stop method that pauses the Timer. You can also give your anonymous AbstractAction class a private boolean field to check if the key is being pressed for the first time or not.
Also, kudos and 1+ for using Key Bindings instead of a KeyListener.
e.g.,
JPanel component = (JPanel) frame.getContentPane();
component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
component.getActionMap().put("space", (new AbstractAction() {
private boolean firstPress = true;
private int timerDelay = 250;
private javax.swing.Timer keyTimer = new javax.swing.Timer(timerDelay , new ActionListener() {
// Swing Timer's actionPerformed
public void actionPerformed(ActionEvent e) {
grid.stepGame();
}
});
// key binding AbstractAction's actionPerformed
public void actionPerformed(ActionEvent e) {
if (firstPress) {
keyTimer.start();
} else {
keyTimer.stop();
}
firstPress = !firstPress;
}
}));
Another useful option is to perform a repeating task on key press and stop it on key release, and this can be done easily by getting the keystrokes for on press and on release:
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true) // for key release
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false) // for key press
For example:
import java.awt.event.*;
import javax.swing.*;
public class SwingTimerEg2 {
private JFrame frame;
private Grid2 grid = new Grid2(this);
private JTextArea textarea = new JTextArea(20, 20);
private int stepCount = 0;
public SwingTimerEg2() {
frame = new JFrame();
textarea.setEditable(false);
frame.add(new JScrollPane(textarea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
setUpKeyBinding();
}
void setUpKeyBinding() {
final int timerDelay = 250;
final Timer keyTimer = new Timer(timerDelay, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
grid.stepGame();
}
});
JPanel component = (JPanel) frame.getContentPane();
final int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
final String spaceDown = "space down";
final String spaceUp = "space up";
component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), spaceDown);
component.getActionMap().put(spaceDown, (new AbstractAction() {
public void actionPerformed(ActionEvent e) {
keyTimer.start();
}
}));
component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), spaceUp);
component.getActionMap().put(spaceUp, (new AbstractAction() {
public void actionPerformed(ActionEvent e) {
keyTimer.stop();
}
}));
}
public void doSomething() {
textarea.append(String.format("Zap %d!!!%n", stepCount));
stepCount ++;
}
private static void createAndShowGui() {
new SwingTimerEg2();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class Grid2 {
private SwingTimerEg2 stEg;
public Grid2(SwingTimerEg2 stEg) {
this.stEg = stEg;
}
void stepGame() {
stEg.doSomething();
}
}
Easiest and dirty solution:
final boolean[] isRunning = new boolean[1];
You don't want to do that—but it works assuming proper synchronization around.
What would be better is
final AtomicBoolean isRunning = new AtomicBoolean();
What would be even better is to review the design once again: global state usually means, "global problems"
The final qualifier requirement can easily be avoided -- replace your inner method (which has the final requirement) with a call to a class method.
No you got the wrong idea about WHY you need final for anonymous classes! Final is only needed for local variables (well more exactly any variable that might have a live time shorter than the given object).
Hence a static variable in a class is perfectly fine and will work perfectly!
Edit: example:
public class Main {
interface Test {
void call();
}
public static volatile boolean running = true;
public static void main(String[] args) {
Test t = new Test() {
#Override
public void call() {
System.out.println(Main.running);
}
};
t.call();
running = false;
t.call();
}
}
Keep a reference to the Timer somewhere, say in your game class.
When the game is paused cancel the Timer.
This will cancel any currently scheduled tasks.
Then when the game is unpaused schedule the timer again as you have done above.
public class Game {
private Timer timer;
public void pause() {
if (timer != null) {
timer.pause();
}
}
public void startOrResumeGame() {
if (timer == null) {
timer = new Timer();
} else {
// Just in case the game was already running.
timer.cancel();
}
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
grid.stepGame();
}
}, 250, 250);
}
}