I\'m doing a Java project where I have to make a GUI that contains multiple timers that countdown from a user input time. When I create only one timer, the program works fine, b
The problem is with your TimerListener
.
Basically, what's happening, is each time it is called, it executes this code block...
if (go[i] == true && !cdt[i].equals(zero)) {
javaTimer[i].start();
cdt[i].dec();
timeDisplay[i].setText(cdt[i].toString());
count[i]++;
}
This means, if you have 3 counters running, this code block will be executed 9 times per second. Three times per counter/Timer
You TimerListener
is too generalised and is trying to do too many things.
Either, create a "single" Timer
that ticks on a regular bases and updates each counter as required (so that each loop is dealing with a single counter) OR modify your TimerListener
so that it takes details about the counter it is managing and deals ONLY with that counter.
Updated with possible solution
This example uses a single, centralised Timer
which simply ticks away in the background. As required, a special Counter
is registered with the Timer
, which then notifies it when a tick
occurs. The Counter
then decrements the CountDownTimer
and updates the UI.
This is an imperfect example, as I would prefer that the CountDownTimer
had a listener interface capable of notifying registered listeners that object's state has changed in some way. This could be achieved through the wrapper Counter
class, but I'm to lazy.
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestTimerPane {
public static void main(String[] args) {
new TestTimerPane();
}
public TestTimerPane() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
CountDownManager countDownManager = new CountDownManager();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
frame.add(new TimerPane(countDownManager), gbc);
frame.add(new TimerPane(countDownManager), gbc);
frame.add(new TimerPane(countDownManager), gbc);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class CountDownManager {
private List<Counter> timers;
public CountDownManager() {
timers = new ArrayList<>(25);
Timer timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Iterator<Counter> it = timers.iterator();
while (it.hasNext()) {
it.next().tick(CountDownManager.this);
}
}
});
timer.start();
}
public void add(Counter timer) {
timers.add(timer);
}
public void remove(Counter timer) {
timers.remove(timer);
}
}
public class TimerPane extends JPanel {
private JTextField fldHours;
private JTextField fldMins;
private JTextField fldSecs;
private JButton btnStart;
private JButton btnStop;
private JButton btnSet;
private JLabel countDown;
private CountDownTimer countDownTimer;
private Counter counter;
public TimerPane(final CountDownManager countDownManager) {
fldHours = new JTextField(2);
fldMins = new JTextField(2);
fldSecs = new JTextField(2);
btnSet = new JButton("Set");
btnStop = new JButton("Stop");
btnStart = new JButton("Start");
countDown = new JLabel("??:??:??");
add(fldHours);
add(new JLabel(":"));
add(fldMins);
add(new JLabel(":"));
add(fldSecs);
add(btnSet);
add(btnStart);
add(btnStop);
add(countDown);
btnSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
countDownTimer = new CountDownTimer(
toInt(fldHours),
toInt(fldMins),
toInt(fldSecs));
counter = new Counter(countDown, countDownTimer);
countDown.setText(countDownTimer.toString());
}
});
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (counter != null) {
countDownManager.add(counter);
}
}
});
btnStop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (counter != null) {
countDownManager.remove(counter);
}
}
});
}
protected int toInt(JTextField field) {
int value = 0;
try {
value = Integer.parseInt(field.getText());
} catch (NumberFormatException exp) {
}
return value;
}
}
public class Counter {
private JLabel output;
private CountDownTimer timer;
public Counter(JLabel output, CountDownTimer timer) {
this.output = output;
this.timer = timer;
}
public void tick(CountDownManager manager) {
timer.dec();
output.setText(timer.toString());
if (timer.convertToSeconds() <= 0) {
manager.remove(this);
}
}
}
public static class CountDownTimer {
/**
* Amount of hours that are being counted down in the timer
*/
int hours;
/**
* Amount of minutes that are being counted down in the timer
*/
int minutes;
/**
* Amount of seconds that are being counted down in the timer
*/
int seconds;
/**
* Boolean that determines if the add method will work. It is changed with
* the toggleSuspend method
*/
static boolean toggle = false;
/**
* ***************************************************************************************
* Default constructor that creates a CountDownTimer object with no time
* contained in it
* ***************************************************************************************
*/
public CountDownTimer() {
hours = 0;
minutes = 0;
seconds = 0;
}
/**
* *******************************************************************************************
* Constructor that uses the input amount of hours, minutes, and seconds to
* count down from
*
* Does not allow time that is negative or allow seconds or minutes that are
* over 60
*
* @param hours amount of hours that will be counted down from
* @param minutes amount of minutes that will be counted down from
* @param seconds amount of seconds that will be counted down from
* *******************************************************************************************
*/
public CountDownTimer(int hours, int minutes, int seconds) {
super();
// doesn't allow negative minutes, seconds, or hours
if (seconds < 0 || minutes < 0 || hours < 0) {
throw new IllegalArgumentException("Time cannot be negative");
}
// doesn't allow seconds that are higher than 60
if (seconds >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 seconds");
}
// doesn't allow minutes that are higher than 60
if (minutes >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 minutes");
}
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
/**
* *******************************************************************
* Constructor that takes minutes and seconds, and sets hours to zero also
* doesn't allow minutes or seconds to be negative or above 60
*
* @param minutes amount of minutes that will be counted down from
* @param seconds amount of seconds that will be counted down from
* ***********************************************************************
*/
public CountDownTimer(int minutes, int seconds) {
super();
// doesn't allow seconds minutes or hours to be negative
if (seconds < 0 || minutes < 0 || hours < 0) {
throw new IllegalArgumentException("Time cannot be negative");
}
// doesn't allow seconds to be greater than 60
if (seconds >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 seconds");
}
// doesn't allow minutes to be greater than 60
if (minutes >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 minutes");
}
this.hours = 0;
this.minutes = minutes;
this.seconds = seconds;
}
/**
* *********************************************************************
* Constructor that only takes seconds and sets hours and minutes to 0 does
* not allow the seconds to be above 60 or negative
*
* @param seconds amount of seconds that will be counted down from
* ***********************************************************************
*/
public CountDownTimer(int seconds) {
super();
// doesn't allow seconds minutes or hours to be negative
if (seconds < 0 || minutes < 0 || hours < 0) {
throw new IllegalArgumentException("Time cannot be negative");
}
// doesn't allow seconds to be greater than 60
if (seconds >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 seconds");
}
this.hours = 0;
this.minutes = 0;
this.seconds = seconds;
}
/**
* Constructor that clones one CountDownTimer object into a new
* CountDownTimer object
*
* @param other The CountDownTimer object that is being cloned
*
*/
public CountDownTimer(CountDownTimer other) {
this.hours = other.hours;
this.minutes = other.minutes;
this.seconds = other.seconds;
}
/**
* *****************************************************************************************************************************
* Constructor that converts a string in the format of "00:00:00" into
* seconds minutes and hours so it can be counted down from
*
* @param startTime String that is converted into seconds minutes and hours
* *****************************************************************************************************************************
*/
public CountDownTimer(String startTime) {
// Separates the seconds minutes and hours into an array
String[] parts = startTime.split(":");
// if the array has only one cell, that means only seconds were input
if (parts.length == 1) {
seconds = Integer.parseInt(parts[0]);
}
// if the array has only 2 cells that means there is only minutes and seconds input
if (parts.length == 2) {
minutes = Integer.parseInt(parts[0]);
seconds = Integer.parseInt(parts[1]);
}
// if the array has 3 cells that means there is seconds minutes and hours input
if (parts.length == 3) {
hours = Integer.parseInt(parts[0]);
minutes = Integer.parseInt(parts[1]);
seconds = Integer.parseInt(parts[2]);
}
// doesn't allow seconds minutes or hours to be negative
if (seconds < 0 || minutes < 0 || hours < 0) {
throw new IllegalArgumentException("Time cannot be negative");
}
// doesn't allow seconds to be greater than or equal to 60
if (seconds >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 seconds");
}
// doesn't allow minutes to be greater than or equal to 60
if (minutes >= 60) {
throw new IllegalArgumentException(
"Cannot have more than 60 minutes");
}
}
/**
* ************************************************************************************************
* Method that returns true or false based on whether or not two
* CountDownTimer objects are equal
*
* @param other Object that is being compared to another CountDownTimer
* ************************************************************************************************
*/
public boolean equals(Object other) {
// converts the two objects to seconds then compares them
if (this.convertToSeconds() == ((CountDownTimer) other)
.convertToSeconds()) {
return true;
}
return false;
}
/**
* ******************************************************************************
* Returns a boolean based on whether two CountDownTimers, t1 and t2, are
* equal
*
* @param t1 first CountDownTimer being compared
* @param t2 second CountDownTimer being compared
* ******************************************************************************
*/
public static boolean equals(CountDownTimer t1, CountDownTimer t2) {
// converts the two objects to seconds and then compares them
if (t1.convertToSeconds() == t2.convertToSeconds()) {
return true;
}
return false;
}
/**
* **********************************************************************************************
* Compares to CountDownTimer objects and returns an int 1, 0, or -1 based
* on whether the first object is greater than, equal to, or less than the
* CountDownTimer in the parameter
*
* @param other CountDownTimer that is being compared
* ***********************************************************************************************
*/
public int compareTo(CountDownTimer other) {
if (this.convertToSeconds() > other.convertToSeconds()) {
return 1;
} else if (this.convertToSeconds() < other.convertToSeconds()) {
return -1;
}
return 0;
}
/**
* ************************************************************************************************
* Compares to CountDownTimer objects and returns an int 1, 0, or -1 based
* on whether the first object (t1) is greater than, equal to, or less than
* the second object (t2)
*
* @param t1 first object being compared
* @param t2 second object being compared
* @return
* *************************************************************************************************
*/
public static int compareTo(CountDownTimer t1, CountDownTimer t2) {
if (t1.convertToSeconds() > t2.convertToSeconds()) {
return 1;
} else if (t1.convertToSeconds() < t2.convertToSeconds()) {
return -1;
}
return 0;
}
/**
* *************************************************************
* subtracts the input amount of seconds from a CountDownTimer
*
* @param seconds amount of seconds the user wants to subtract
* *************************************************************
*/
public void subtract(int seconds) {
// places the amount of seconds into an integer
int tempSeconds = this.convertToSeconds();
// subtracts the input seconds from the seconds that were converted
tempSeconds -= seconds;
// converts the new seconds back into the object
formatSeconds(tempSeconds);
}
/**
* *****************************************************************************************
* Subtracts the amount of time contained in one CountDownTimer from another
* CountDownTimer
*
* @param other CountDownTimer that is doing the subtracting
* *****************************************************************************************
*/
public void subtract(CountDownTimer other) {
int otherSeconds = other.convertToSeconds();
this.subtract(otherSeconds);
}
/**
* *********************************************************************
* Adds seconds to the object based on what is put into the parameter
*
* @param seconds amount of seconds being added to the CountDownTimer
* **********************************************************************
*/
public void add(int seconds) {
// keeps the method from adding when the toggle is activated
if (toggle == false) {
int tempSeconds = this.convertToSeconds();
tempSeconds += seconds;
formatSeconds(tempSeconds);
} else {
throw new IllegalArgumentException(
"Cannot use add when toggle is enabled");
}
}
/**
* Adds the seconds from one CountDownTimer to another CountDownTimer
*
* @param other CountDownTimer that is being added to another CountDowntimer
*/
public void add(CountDownTimer other) {
// doesn't allow the method to add when the toggle is true
if (toggle == false) {
int otherSeconds = other.convertToSeconds();
this.add(otherSeconds);
} else {
throw new IllegalArgumentException(
"Cannot use add when toggle is enabled");
}
}
/**
* *****************************************
* Decreases the CountDownTimer by 1 second
* *****************************************
*/
public void dec() {
int tempSeconds = this.convertToSeconds();
tempSeconds--;
formatSeconds(tempSeconds);
}
/**
* **************************************************
* Increases the CountDownTimer object by 1 second
* *************************************************
*/
public void inc() {
int tempSeconds = this.convertToSeconds();
tempSeconds--;
formatSeconds(tempSeconds);
}
/**
* Returns the object as a string in the format of "00:00:00"
*/
public String toString() {
String time = "" + this.hours + ":";
if (this.minutes < 10) {
time += "0" + this.minutes + ":";
} else {
time += this.minutes + ":";
}
if (this.seconds < 10) {
time += "0" + this.seconds;
} else {
time += this.seconds;
}
return time;
}
/**
* **********************************************
* Saves the object with a specified name
*
* @param fileName name of the file being saved
* ***********************************************
*/
public void save(String fileName) {
PrintWriter out = null;
try {
out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
} catch (IOException e) {
e.printStackTrace();
}
out.println(this.hours);
out.println(this.minutes);
out.println(this.seconds);
out.close();
}
/**
* ************************************************
* Loads the object with the specified name
*
* @param fileName Name of the file being loaded
* ************************************************
*/
public void load(String fileName) {
try {
Scanner fileReader = new Scanner(new File(fileName));
this.hours = fileReader.nextInt();
this.minutes = fileReader.nextInt();
this.seconds = fileReader.nextInt();
fileReader.close();
System.out.println("Hours: " + this.hours);
System.out.println("Minutes: " + this.minutes);
System.out.println("Seconds: " + this.seconds);
} catch (FileNotFoundException error) {
System.out.println("File not found");
} catch (IOException error) {
System.out.println("OH NO THAT WAS NOT SUPPOSED TO HAPPEN");
}
}
/**
* ********************************************************************************************
* Switches the toggle boolean, and doesn't allow the add methods to work
* when it is activated
* **********************************************************************************************
*/
public static void toggleSuspend() {
if (toggle == false) {
toggle = true;
}
if (toggle == true) {
toggle = false;
}
}
/**
* *********************************************************************************
* Formats a certain amount of seconds and puts it into an existing
* CountDownTimer
*
* @param seconds seconds being formatted
* *********************************************************************************
*/
private void formatSeconds(int seconds) {
this.hours = seconds / 3600;
seconds %= 3600;
this.minutes = seconds / 60;
this.seconds = seconds % 60;
}
/**
* ***************************************************************************
* Returns the amount of seconds that are contained in a CountDownTime
* object
* ***************************************************************************
*/
private int convertToSeconds() {
int hSeconds = hours * 3600;
int mSeconds = minutes * 60;
return hSeconds + mSeconds + seconds;
}
}
}
Now, if you need a separate Timer
per CountDownTimer
, then I would probably create some kind of "worker" class that would take a CountDownTimer
and that had a self contained Timer
. I would also provide some kind of listener that interested parties could register to and would allow them to update the UI as they see fit...