Hi I am trying to create a StopWatch app for one of the project questions in the Java Foundations Textbook. So far I have written this code:
package StopWatch;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class StopWatchPanel extends JPanel{
private JPanel userPanel = new JPanel();
private JLabel timeDisplay = new JLabel("00:00:00", SwingConstants.CENTER);
private JButton startButton = new JButton("Start");
private JButton stopButton = new JButton("Stop");
private JButton resetButton = new JButton("Reset");
private Timer refreshRate = new Timer(1, new ActionListen());
private int milliseconds, seconds, minutes, hours;
public StopWatchPanel(){
// user panel settings
userPanel.setPreferredSize(new Dimension(400, 100));
userPanel.setBackground(new Color(200, 200, 255));
// add listeners
startButton.addActionListener(new ActionListen());
stopButton.addActionListener(new ActionListen());
resetButton.addActionListener(new ActionListen());
// component setting
timeDisplay.setPreferredSize(new Dimension(400, 50));
// add components
userPanel.add(timeDisplay);
userPanel.add(startButton);
userPanel.add(stopButton);
userPanel.add(resetButton);
// add to main frame
add(userPanel);
}
private int milsec(){
if(milliseconds > 1000){
milliseconds = 0;
seconds++;
}
return milliseconds++;
}
private int sec(){
if(seconds > 59){
seconds = 0;
minutes++;
}
return seconds;
}
private int min(){
if(minutes > 59){
minutes = 0;
hours++;
}
return minutes;
}
private int hrs(){
if(hours > 23){
hours = 0;
}
return hours;
}
private class ActionListen implements ActionListener{
public void actionPerformed(ActionEvent e){
if(e.getSource().equals(startButton)){
refreshRate.start();
}
else if(e.getSource().equals(refreshRate)){
timeDisplay.setText(sec()+":"+milsec());
}
else if(e.getSource().equals(stopButton)){
refreshRate.stop();
}
else if(e.getSource().equals(resetButton)){
timeDisplay.setText("00:00:00");
milliseconds = 0;
seconds = 0;
minutes = 0;
hours = 0;
}
}
}
}
it works if i set the delay on the timer to 1000ms therefore showing seconds (if i exclude the milsec method) but i want to display milliseconds as well. So I put delay to 1ms so that after each 1ms, it will setText to current count of second (sec method).
But for some reason this doesnt work because the numbers add up too slowly considering that 1000 milliseconds = 1 second.
What am i doing wrong?
The delay of 1 ms means that the timer will generate events every 1 millisecond. The events are then dispatched to the event dispatch thread, which introduces a delay (since there's other code also running on the event dispatch thread), so the actual interval between the calls to actionPerformed() is likely to be larger than 1 ms.
If you need your clock to be precise, you need to measure the exact elapsed time between calls (using, for example, the System.currentTimeMillis() method), rather than increment the time by 1 ms on every call.
You can improve the timeliness of the output by varying the millisecond interval. Instead of 1ms, you can try 10, 20 or 50ms interval. The code below uses 100ms.
package StopWatch;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class StopWatchPanel extends JPanel {
private JPanel userPanel = new JPanel();
private JLabel timeDisplay = new JLabel("00:00:00", SwingConstants.CENTER);
private JButton startButton = new JButton("Start");
private JButton stopButton = new JButton("Stop");
private JButton resetButton = new JButton("Reset");
private Timer refreshRate = new Timer(100, new ActionListen());
private int milliseconds, seconds, minutes, hours;
public StopWatchPanel(){
// user panel settings
userPanel.setPreferredSize(new Dimension(400, 100));
userPanel.setBackground(new Color(200, 200, 255));
// add listeners
startButton.addActionListener(new ActionListen());
stopButton.addActionListener(new ActionListen());
resetButton.addActionListener(new ActionListen());
// component setting
timeDisplay.setPreferredSize(new Dimension(400, 50));
// add components
userPanel.add(timeDisplay);
userPanel.add(startButton);
userPanel.add(stopButton);
userPanel.add(resetButton);
// add to main frame
add(userPanel);
}
private int milsec(){
milliseconds+=100;
if(milliseconds >= 1000){
milliseconds = 0;
seconds++;
}
return milliseconds;
}
private int sec(){
if(seconds > 59){
seconds = 0;
minutes++;
}
return seconds;
}
private int min(){
if(minutes > 59){
minutes = 0;
hours++;
}
return minutes;
}
private int hrs(){
if(hours > 23){
hours = 0;
}
return hours;
}
private class ActionListen implements ActionListener{
public void actionPerformed(ActionEvent e){
if(e.getSource().equals(startButton)){
refreshRate.start();
}
else if(e.getSource().equals(refreshRate)){
int tms = milsec();
int tsec = sec();
int tmin = min();
int thr = hrs();
timeDisplay.setText(thr + ":" +tmin+ ":" +tsec+ "." +tms/100);
}
else if(e.getSource().equals(stopButton)){
refreshRate.stop();
}
else if(e.getSource().equals(resetButton)){
timeDisplay.setText("00:00:00");
milliseconds = 0;
seconds = 0;
minutes = 0;
hours = 0;
}
}
}
}
I believe the original code was primarily made for demo purposes and does not concern too much on the accurateness of the time. If you want a more realtime output, you will need to try a different approach. In such case you may use the System.currentTimeMillis() as pointed out by the other answer.
Related
I've been working on this stopwatch application for a decent amount of time now but I've encountered some problems.
Problems
The stopwatch isn't starting properly probably due to the clock logic
The stopwatch isn't displaying the numbers onto my Java program properly
It would be nice if anyone were to help explain the flaws in my program and tell me why it isn't working the way I would like it to be. Much help needed and appreciated so here is my code.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Clock {
private static void createWindow() {
// Important
final int windowWidth = 800; // Window width
final int windowHeight = 300; // Window height
// Clock variables
boolean clockRunning = true; // When clock is running, this will be true
int milliseconds = 0;
int seconds = 0;
int minutes = 0;
int hours = 0;
// Create JFrame
JFrame frame = new JFrame(); // JFrame object
// Create timer text
JLabel timer = new JLabel("00:00:00");
timer.setText("00:00:00");
timer.setBounds(355, 100, 100, 40); // Button position
// JButtons
JButton startTimer = new JButton("Start the timer"); // Start timer button
JButton stopTimer = new JButton("Stop the timer"); // Stop timer button
// Event listeners
startTimer.addActionListener(new ActionListener() // Start timer
{
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println("Timer has started");
}
});
stopTimer.addActionListener(new ActionListener() // Stop timer
{
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println("Timer has stoped");
}
});
// Clock logic
if (clockRunning = true) {
milliseconds = 0;
seconds++;
}
if(milliseconds > 1000) {
milliseconds=0;
seconds++;
}
if(seconds > 60) {
milliseconds=0;
seconds=0;
minutes++;
}
if(minutes > 60) {
milliseconds=0;
minutes=0;
hours++;
}
timer.setText(" : " + seconds); // Milliseconds
timer.setText(" : " + milliseconds); // Seconds
timer.setText(" : " + minutes); // Minutes
timer.setText("" + hours); // Hours
// JButton Settings
startTimer.setBounds(10, 100, 200, 30); // Button position
stopTimer.setBounds(570, 100, 200, 30); // Button position
// Frame Settings
frame.setSize(windowWidth, windowHeight); // Window size
frame.setLayout(null); // Frame position
// Add
frame.add(startTimer);
frame.add(stopTimer);
frame.add(timer);
// Frame settings
frame.setVisible(true); // Make frame visible
frame.setResizable(false); // Disables maximize
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Allows the window to be closed
}
public static void main(String[] args) {
createWindow();
}
}
The stopwatch isn't displaying the numbers onto my Java program properly
timer.setText(" : " + seconds); // Milliseconds
timer.setText(" : " + milliseconds); // Seconds
timer.setText(" : " + minutes); // Minutes
timer.setText("" + hours); // Hours
The setText(…) method replaces the existing text.
So the above code is the same as:
timer.setText("" + hours); // Hours
The stopwatch isn't starting properly probably due to the clock logic
For a stop watch you will need to use a Swing Timer so you can update the text at a specified interval.
See: Program freezes during Thread.sleep() and with Timer for the basics of using a Timer.
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 want to use a Timer in a while loop. That is, I want to run the body of a while loop multiple times when it includes a timer within it. However, whenever I do this, the body of the while loop seems to execute and increment before the timer has fired the required amount of times. It is supposed to fire 15 times before the while loop increments, but it seems to be running through the while loop immediately before the timer fires the required amount of times, which I do not understand.
I know it isn't a problem with anything else because when I just run the code through once (i.e., without a while loop), it runs fine. The idea is that a JLabel is supposed to be removed and replaced every 1300ms until 15 images are displayed. Once it displays the 15 images, it moves onto the next row of the 2D array, of which there are 60 rows in total.
Any help would be appreciated. I have put the code below.
public class Game implements KeyListener, ActionListener{
private Preparation prep;
private Window window;
private JLabel earthLabel;
private JPanel panel;
private Timer timer;
private JLabel[] arrayOfAllSpaceshipsLabels;
private int[] arrayOfAllSpaceshipsIndexes;
private JLabel currentShipLabel;
private int numberOfBlocks;
private int firings;
private int[][] blocks = new int[60][15] //the values in this array have been initalized in another class that is too big and not relevant to my problem
public Game(){
window = new Window("Space Game");
runBlock();
}
private void runBlock() {
int blocksLength = blocks.length;
numberOfBlocks = 0;
while(numberOfBlocks < blocksLength){
firings = 0;
timer = new Timer(1333, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (firings == 15){
panel.removeAll();
panel.revalidate();
panel.repaint();
timer.stop();
System.out.println("Block finished");
return;
}
panel.removeAll(); /*(currentShipLabel);*/
panel.revalidate();
panel.repaint();
currentShipLabel = arrayOfAllSpaceshipsLabels[blocks[numberOfBlocks][firings]];
panel.add(currentShipLabel);
panel.validate();
firings++;
}
});
timer.start();
}
numberOfBlocks++;
}
}
public class Window extends JFrame{
private JPanel panel;
public Window(String title){
SwingUtilities.isEventDispatchThread();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//this.setBackground(Color.BLACK);
panel = new JPanel();
this.add(panel);
this.pack();
this.setSize(500,500); //my edit
panel.setBackground(Color.BLACK);
this.setVisible(true);
}
public JPanel getPanel() {
// TODO Auto-generated method stub
return panel;
}
}
As you have noticed, creating and starting a Timer does not prevent your loop from continuing.
To solve your problem you have to "move" your while loop into the timer action:
private void runBlock() {
int blocksLength = blocks.length;
numberOfBlocks = 0;
timer = new Timer(1333, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (firings == 15){
if (numberOfBlocks == blocksLength)
panel.removeAll();
panel.revalidate();
panel.repaint();
timer.stop();
System.out.println("Block finished");
return;
} else {
firings = 0;
numberOfBlocks++;
}
}
panel.removeAll(); /*(currentShipLabel);*/
panel.revalidate();
panel.repaint();
currentShipLabel = arrayOfAllSpaceshipsLabels[blocks[numberOfBlocks][firings]];
panel.add(currentShipLabel);
panel.revalidate();
firings++;
}
});
timer.start();
}
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");
}
}
}
}
There are two buttons in GUI: "Go" and "Stop". There is also a JLabel "trainInfoDetail". When "Go" is clicked, how do I let the "speed" in JLabel update once per second while the "Stop" button is still listening and always really to terminate the loop? My program, while looping, seems to stack in this loop and disables "Stop" button until it finishes.
myButton1 = new JButton("Go!",icon1);
myButton2 = new JButton("Stop!",icon2);
HandlerClass onClick = new HandlerClass();
myButton1.addActionListener(onClick);
myButton2.addActionListener(onClick);
private class HandlerClass implements ActionListener{
//overwriting
public void actionPerformed(ActionEvent event){
long startTime = System.currentTimeMillis();
long now;
String caller = event.getActionCommand();
if(caller.equals("Go!")){
while(speed < 100){
now = System.currentTimeMillis();
if((now - startTime) >= 1000){
speed += 10;
trainInfoDetail.setText("Speed: "+speed+" mph");
startTime = now;
}
}
}
else if (caller.equals("Stop!")){
speed = 0;
trainInfoDetail.setText("Speed: "+speed+" mph");
}
}
}
after using Timer:
public class HandlerClass implements ActionListener{
//overwriting
public void actionPerformed(ActionEvent event){
String caller = event.getActionCommand();
TimeClass tc = new TimeClass(caller);
timer = new Timer(1000, tc);
timer.setInitialDelay(1000);
timer.start();
}
}
public class TimeClass implements ActionListener{
String caller;
public TimeClass(String caller){
this.caller=caller;
}
//overwriting
public void actionPerformed(ActionEvent event){
if(caller.equals("Go!")){
speed += 10;
trainInfoDetail.setText("Speed: "+speed+" mph");
}
else if (caller.equals("Stop!")){
timer.stop();
}
else{
timer.stop();
//return;
}
}
}
Short answer, use a Swing Timer. See How to use Swing Timers for more details.
Basically, you can setup the Timer to tick every second (or whatever interval you need) and update the state/variables and UI safely, without blocking the UI or violating the thread rules of Swing and you can stop the Timer in your "stop" button simply by calling Timer's stop method
You should also have a look Concurrency in Swing