How to make a pause-able count up timer in Java? - java

I'm trying to make a timer that will count up from 00h:00m:00s, with the ability to pause and restart the count from its current time.
Heres the solution I currently have: It will always restart the timer from 0 instead of continuing where it left off. Also, for another inexplicable reason, the hours always display as 07 instead of 00. Does anyone know how I could fix these issues, to have it start counting up from its previous value, and display the correct amount of hours elapsed?
private final SimpleDateFormat date = new SimpleDateFormat("KK:mm:ss");
private long startTime = 0;
private final ClockListener clock = new ClockListener();
private final Timer normalTimer = new Timer(53, clock);
startTimerButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(startTime == 0) {
startTime = System.currentTimeMillis();
}
else {
startTime += (System.currentTimeMillis() - startTime);
}
normalTimer.start();
}
});
stopTimerButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
normalTimer.stop();
}
});
private void updateClock(){
Date elapsed = new Date(System.currentTimeMillis() - startTime);
timerText.setText(date.format(elapsed));
}
private class ClockListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
updateClock();
}
}

Introduction
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.
Your code wasn't runnable, so I created the following GUI.
When you press the "Start" button, the timer starts counting. 37 seconds.
2 minutes, 7 seconds.
1 hour, 6 minutes, 11 seconds.
Pressing the "Pause button pauses the count. Pressing the "Restart" button resumes the count. You can pause and resume the count as many times as you want.
Pressing the "Stop" button stops the counter. You can press the "Reset" button to reset the counter before starting again.
Explanation
When creating a Swing application, using the model-view-controller (MVC) pattern helps to separate your concerns and allows you to focus on one part of the application at a time.
Creating the application model made creating the GUI much easier. The application model is made up of one or more plain Java getter/setter classes.
The CountupTimerModel class keeps long fields to hold the duration in milliseconds and the previous duration. This way, I don't have to pause and restart the Swing Timer.
The duration is the difference between the current time and the start time. I use the System.currentTimeMillis() method to calculate the duration.
The GUI is fairly straightforward.
I made the JButton ActionListeners lambdas. I made the TimerListener a separate class to move the code out of that particular lambda.
Code
Here's the complete runnable code. I made the additional classes inner classes so I could post the code as one block.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class CountupTimerGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new CountupTimerGUI());
}
private final CountupTimerModel model;
private JButton resetButton, pauseButton, stopButton;
private JLabel timerLabel;
public CountupTimerGUI() {
this.model = new CountupTimerModel();
}
#Override
public void run() {
JFrame frame = new JFrame("Countup Timer GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createDisplayPanel(), BorderLayout.NORTH);
frame.add(createButtonPanel(), BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createDisplayPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
Font font = panel.getFont().deriveFont(Font.BOLD, 48f);
timerLabel = new JLabel(model.getFormattedDuration());
timerLabel.setFont(font);
panel.add(timerLabel);
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
Font font = panel.getFont().deriveFont(Font.PLAIN, 16f);
resetButton = new JButton("Reset");
resetButton.setFont(font);
panel.add(resetButton);
resetButton.addActionListener(event -> {
model.resetDuration();
timerLabel.setText(model.getFormattedDuration());
});
pauseButton = new JButton("Restart");
pauseButton.setFont(font);
panel.add(pauseButton);
pauseButton.addActionListener(event -> {
String text = pauseButton.getText();
if (text.equals("Pause")) {
model.pauseTimer();
pauseButton.setText("Restart");
} else {
model.startTimer();
pauseButton.setText("Pause");
}
});
Timer timer = new Timer(200,
new CountupListener(CountupTimerGUI.this, model));
stopButton = new JButton("Start");
stopButton.setFont(font);
panel.add(stopButton);
stopButton.addActionListener(event -> {
String text = stopButton.getText();
if (text.equals("Start")) {
model.resetDuration();
model.startTimer();
timer.start();
resetButton.setEnabled(false);
stopButton.setText("Stop");
} else {
model.stopTimer();
timer.stop();
resetButton.setEnabled(true);
stopButton.setText("Start");
}
});
Dimension d = getLargestJButton(resetButton, pauseButton, stopButton);
resetButton.setPreferredSize(d);
pauseButton.setPreferredSize(d);
stopButton.setPreferredSize(d);
pauseButton.setText("Pause");
return panel;
}
private Dimension getLargestJButton(JButton... buttons) {
Dimension largestDimension = new Dimension(0, 0);
for (JButton button : buttons) {
Dimension d = button.getPreferredSize();
largestDimension.width = Math.max(largestDimension.width, d.width);
largestDimension.height = Math.max(largestDimension.height,
d.height);
}
largestDimension.width += 10;
return largestDimension;
}
public JButton getResetButton() {
return resetButton;
}
public JButton getPauseButton() {
return pauseButton;
}
public JButton getStopButton() {
return stopButton;
}
public JLabel getTimerLabel() {
return timerLabel;
}
public class CountupListener implements ActionListener {
private final CountupTimerGUI view;
private final CountupTimerModel model;
public CountupListener(CountupTimerGUI view, CountupTimerModel model) {
this.view = view;
this.model = model;
}
#Override
public void actionPerformed(ActionEvent event) {
model.setDuration();
view.getTimerLabel().setText(model.getFormattedDuration());
}
}
public class CountupTimerModel {
private boolean isRunning;
private long duration, previousDuration, startTime;
public CountupTimerModel() {
resetDuration();
}
public void resetDuration() {
this.duration = 0L;
this.previousDuration = 0L;
this.isRunning = true;
}
public void startTimer() {
this.startTime = System.currentTimeMillis();
this.isRunning = true;
}
public void pauseTimer() {
setDuration();
this.previousDuration = duration;
this.isRunning = false;
}
public void stopTimer() {
setDuration();
this.isRunning = false;
}
public void setDuration() {
if (isRunning) {
this.duration = System.currentTimeMillis() - startTime
+ previousDuration;
}
}
public String getFormattedDuration() {
int seconds = (int) ((duration + 500L) / 1000L);
int minutes = seconds / 60;
int hours = minutes / 60;
StringBuilder builder = new StringBuilder();
if (hours > 0) {
builder.append(hours);
builder.append(":");
}
minutes %= 60;
if (hours > 0) {
builder.append(String.format("%02d", minutes));
builder.append(":");
} else if (minutes > 0) {
builder.append(minutes);
builder.append(":");
}
seconds %= 60;
if (hours > 0 || minutes > 0) {
builder.append(String.format("%02d", seconds));
} else {
builder.append(seconds);
}
return builder.toString();
}
}
}

Related

Why is my looping GUI timer not showing up?

I'm trying to make a GUI timer without using javax.swing.Timer(kind of a strange task), but I am having trouble making it work. It's supposed to sleep the thread for 1 second, add 1 to seconds, and repeat(infinitely). When I run my program, the icon shows up, but the window does not appear. I'm guessing my error is in the Thread.sleep(1000); line or in that area, but I'm not sure why it doesn't work. Is Thread.sleep(millis)not compatible with swing applications? Do I have to multithread? Here's my program:
import java.awt.*;
import javax.swing.*;
public class GUITimer extends JFrame {
private static final long serialVersionUID = 1L;
private int seconds = 0;
public GUITimer() {
initGUI();
pack();
setVisible(true);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void initGUI(){
JLabel title = new JLabel("Timer");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
title.setFont(titleFont);
title.setHorizontalAlignment(JLabel.CENTER);
title.setBackground(Color.BLACK);
title.setForeground(Color.WHITE);
title.setOpaque(true);
add(title, BorderLayout.NORTH);
JLabel timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
add(timeDisplay, BorderLayout.CENTER);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
seconds++;
initGUI();
}
public static void main(String[] args) {
try {
String className = UIManager.getCrossPlatformLookAndFeelClassName();
UIManager.setLookAndFeel(className);
}
catch (Exception e) {}
EventQueue.invokeLater(new Runnable() {
public void run() {
new GUITimer();
}
});
}
}
EDIT:
I noticed when I print seconds in my method initGUI() to console, it prints them incrementally by one second correctly. So when it looks like:
private void initGUI() {
System.out.println(seconds);
//...
it prints the value of seconds after every second(How the JLabel should). This shows that my loop is working fine, and my Thread.sleep(1000) is OK also. My only problem now, is that the frame is not showing up.
Your main window does not appear, because you called infinite recursion inside constructor. GUITimer will not be created and this lock main thread.
You need use multithreading for this aim. Main thread for drawing time, second thread increment and put value to label
For example:
import javax.swing.*;
import java.awt.*;
public class GUITimer extends JFrame
{
private static final long serialVersionUID = 1L;
private int seconds = 0;
private Thread timerThread;
private JLabel timeDisplay;
public GUITimer()
{
initGUI();
pack();
setVisible(true);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void initGUI()
{
JLabel title = new JLabel("Timer");
Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
title.setFont(titleFont);
title.setHorizontalAlignment(JLabel.CENTER);
title.setBackground(Color.BLACK);
title.setForeground(Color.WHITE);
title.setOpaque(true);
add(title, BorderLayout.NORTH);
timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
add(timeDisplay, BorderLayout.CENTER);
}
public void start()
{
seconds = 0;
timerThread = new Thread(new Runnable()
{
#Override
public void run()
{
while(true)
{
timeDisplay.setText(Integer.toString(seconds++));
try
{
Thread.sleep(1000L);
}
catch(InterruptedException e) {}
}
}
});
timerThread.start();
}
public void stop()
{
timerThread.interrupt();
}
public static void main(String[] args)
{
try
{
GUITimer timer = new GUITimer();
timer.start();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
The core issue is, you're blocking the UI by continuously calling initGUI, which will eventually fail with a StackOverFlowException, as the method calls never end
The preference would be to use a Swing Timer, but since you've stated you don't want to do that, a better solution would be to use a SwingWorker, the reason for this - Swing is NOT thread safe and SwingWorker provides a convenient mechanism for allowing us to update the UI safely.
Because both Swing Timer and Thead.sleep only guarantee a minimum delay, they are not a reliable means for measuring the passage of time, it would be better to make use of Java 8's Date/Time API instead
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label = new JLabel("00:00:00");
private TimeWorker timeWorker;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(label, gbc);
JButton button = new JButton("Start");
add(button, gbc);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeWorker == null) {
timeWorker = new TimeWorker(label);
timeWorker.execute();
button.setText("Stop");
} else {
timeWorker.cancel(true);
timeWorker = null;
button.setText("Start");
}
}
});
}
}
public class TimeWorker extends SwingWorker<Void, Duration> {
private JLabel label;
public TimeWorker(JLabel label) {
this.label = label;
}
#Override
protected Void doInBackground() throws Exception {
LocalDateTime startTime = LocalDateTime.now();
Duration totalDuration = Duration.ZERO;
while (!isCancelled()) {
LocalDateTime now = LocalDateTime.now();
Duration tickDuration = Duration.between(startTime, now);
publish(tickDuration);
Thread.sleep(500);
}
return null;
}
#Override
protected void process(List<Duration> chunks) {
Duration duration = chunks.get(chunks.size() - 1);
String text = format(duration);
label.setText(text);
}
public String format(Duration duration) {
long hours = duration.toHours();
duration = duration.minusHours(hours);
long minutes = duration.toMinutes();
duration = duration.minusMinutes(minutes);
long millis = duration.toMillis();
long seconds = (long)(millis / 1000.0);
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
}
}

Making a countdown timer that doesn't interrupt the entire program?

So I'm trying to make a reaction game where you press start button, a hidden timer that will countdown to zero from a random number between 1 & 10 seconds. Most answer regarding java timers recommend using
Thread.sleep(1000);
However this interrupts the entire program while I just wants it to countdown. How should I solve this?
After pressing start and the program has counted down from a random number. The blue icon (entire code below) will turn red and then you're supposed to press it and it will display the time it took for you to press it.
Code is focus:
public void countDown() throws InterruptedException {
int random = r.nextInt((10000 - 1000) + 1) + 1000;
while(random >= 0){
Thread.sleep(1000);
random -= 1000;
}
if (random <= 0) {
button_1.setIcon(img_react);
repaint();
}
}
Image files used:
http://imgur.com/DjI8Udr
http://imgur.com/XKQW6DI
Entire code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;
import java.util.*;
public class Interface extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
ImageIcon img_idle = new ImageIcon("img_idle.png");
ImageIcon img_react = new ImageIcon("img_react.png");
JButton button_1 = new JButton(img_idle);
JButton start = new JButton("Start");
Random r = new Random();
public Interface() {
super("Simple Reaction Game");
setSize(180, 350);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
Container contentArea = getContentPane();
contentArea.setBackground(Color.white);
FlowLayout flowManager = new FlowLayout();
contentArea.setLayout(flowManager);
button_1.addActionListener(this);
start.addActionListener(this);
contentArea.add(button_1);
contentArea.add(start);
setContentPane(contentArea);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == start) {
try {
countDown();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
public void countDown() throws InterruptedException {
Thread t = new Thread();
int random = r.nextInt((10000 - 1000) + 1) + 1000;
while(random >= 0){
t.sleep(1000);
random -= 1000;
}
if (random <= 0) {
button_1.setIcon(img_react);
repaint();
}
}
public static void main(String args[]) {
new Interface();
}
}
You can't just use Thread.sleep(), it won't work as you think it does. Instead, you can use javax.swing.Timer which is made to do what you're trying to do.
Some notes from the docs (if you didn't bother reading it):
Timers perform their waiting using a single, shared thread.
Timers can safely perform operations on Swing components.
Timers can safely perform operations on Swing components.
The javax.swing.Timer has two features that can make it a little easier to use with GUIs.
I've modified an example from here to show how you can adapt it to your needs. It's using your random generated number which is generated each time the timer is finished and you press "start".
import java.awt.*;
import java.awt.event.*;
import java.text.SimpleDateFormat;
import javax.swing.*;
import javax.swing.UnsupportedLookAndFeelException;
import java.util.Random;
public class Interface {
public static void main(String[] args) {
new Interface();
}
public Interface() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Timer timer;
private long startTime = -1;
private long duration;
private JLabel label;
private JButton start;
public TestPane() {
start = new JButton("Start");
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime < 0) {
startTime = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
long clockTime = now - startTime;
if (clockTime >= duration) {
clockTime = duration;
timer.stop();
}
SimpleDateFormat df = new SimpleDateFormat("mm:ss:SSS");
label.setText(df.format(duration - clockTime));
}
});
timer.setInitialDelay(0);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!timer.isRunning()) {
duration = new Random().nextInt((10000 - 1000) + 1) + 1000;
startTime = -1;
timer.start();
}
}
});
label = new JLabel("...");
add(label);
add(start);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 250);
}
}
}
I would suggest a different architecture. Why do you think you need to "count down" anything?!
Meaning: all you care about is that you want to do "something" after a random period of time; so something like this (using pseudo-code) might be easier in the end:
now = ... get current time
then = now + random value
schedule the Swing timer to sent some Event X "then"
And simply have some code that that reacts to incoming X events.

How can create timer with JLabel?

I want to display in my JPanel a JLabel with timer in this mode, for example:
03:50 sec
03:49 sec
....
....
00:00 sec
So I have build this code:
#SuppressWarnings("serial")
class TimeRefreshRace extends JLabel implements Runnable {
private boolean isAlive = false;
public void start() {
Thread t = new Thread(this);
isAlive = true;
t.start();
}
public void run() {
int timeInSecond = 185
int minutes = timeInSecond/60;
while (isAlive) {
try {
//TODO
} catch (InterruptedException e) {
log.logStackTrace(e);
}
}
}
}//fine autoclass
And with this code, I can start the JLabel
TimeRefreshRace arLabel = new TimeRefreshRace ();
arLabel.start();
So I have the time in secondo for example 180 second, how can I create the timer?
Here is an example, how to build a countdown label. You can use this pattern to create your component.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.WindowConstants;
public class TimerTest {
public static void main(String[] args) {
final JFrame frm = new JFrame("Countdown");
final JLabel countdownLabel = new JLabel("03:00");
final Timer t = new Timer(1000, new ActionListener() {
int time = 180;
#Override
public void actionPerformed(ActionEvent e) {
time--;
countdownLabel.setText(format(time / 60) + ":" + format(time % 60));
if (time == 0) {
final Timer timer = (Timer) e.getSource();
timer.stop();
}
}
});
frm.add(countdownLabel);
t.start();
frm.pack();
frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frm.setVisible(true);
}
private static String format(int i) {
String result = String.valueOf(i);
if (result.length() == 1) {
result = "0" + result;
}
return result;
}
}
You could within your try block call the Event Dispatcher Thread (EDT) and update your UI:
try {
SwingUtils.invokeLater(new Runnable() {
#Override
public void run() {
this.setText(minutes + " left");
}
}
//You could optionally block your thread to update your label every second.
}
Optionally, you could use a Timer instead of an actual thread, so your TimerRefreshRace will have its own timer which periodically fires an event. You would then use the same code within your try-catch block to update the UI.

Why is my JLabel not showing up

I am calling this method called check in one of my abstract classes but for some reason the JLabel (problem) I am adding to the JPanel (panel) is not showing up.
Why is this occurring? Any explanations, I am using both the repaint, and validate methods but still nothing shows up.
The problem you're having is you're blocking the Event Dispatching Thread, prevent the UI from been updated or any new events from been processed...
It starts here...
for(int i = 0; i < 15; i++)
{
//...
//Check to see if user has enetered anything
// And is compounded here
while(!answered)
{
Thread.sleep(duration);
//...
}
You're clearly thinking in a procedural manner (like you would for a console program), but this isn't how GUIs work, GUIs are event driven, something happens at some point in time and you respond to it.
My suggestion is to investigate Swing Timer, which will allow you to schedule a call back at some, point in the future and perform some action when it is triggered, which can be used to modify the UI, as its executed within the context of the EDT.
See Concurrency in Swing and How to use Swing Timers for more details
I'd also recommend that you take a look at CardLayout, as it might make easier to change the between different views
See How to Use CardLayout for more details
Basics
I work very much to the principle of "Code to interface not implementation" and the Model-View-Controller. These basically encourage your to separate and isolate responsibility, so a change in one part won't adversely affect another.
It also means you can plug'n'play implementations, decoupling the code and making it more flexible.
Start with the basic, you need something that has some text (the question), a correct answer and some "options" (or incorrect answers)
public interface Question {
public String getPrompt();
public String getCorrectAnswer();
public String[] getOptions();
public String getUserResponse();
public void setUserResponse(String response);
public boolean isCorrect();
}
So, pretty basic. The question has a prompt, a right answer, some wrong answers and can manage the user response. For ease of use, it also has a isCorrect method
Now, we need an actual implementation. This is a pretty basic example, but you might have a number of different implementations and might even include generics for the type of answers (which I've assumed as String for argument sake)
public class DefaultQuestion implements Question {
private final String prompt;
private final String correctAnswer;
private final String[] options;
private String userResponse;
public DefaultQuestion(String prompt, String correctAnswer, String... options) {
this.prompt = prompt;
this.correctAnswer = correctAnswer;
this.options = options;
}
#Override
public String getPrompt() {
return prompt;
}
#Override
public String getCorrectAnswer() {
return correctAnswer;
}
#Override
public String[] getOptions() {
return options;
}
#Override
public String getUserResponse() {
return userResponse;
}
#Override
public void setUserResponse(String response) {
userResponse = response;
}
#Override
public boolean isCorrect() {
return getCorrectAnswer().equals(getUserResponse());
}
}
Okay, that's all fine and all, but what does this actually do for us? Well, know you can create a simple component whose sole job is to present the question to the user and handle their response...
public class QuestionPane extends JPanel {
private Question question;
public QuestionPane(Question question) {
this.question = question;
setLayout(new BorderLayout());
JLabel prompt = new JLabel("<html><b>" + question.getPrompt() + "</b></html>");
prompt.setHorizontalAlignment(JLabel.LEFT);
add(prompt, BorderLayout.NORTH);
JPanel guesses = new JPanel(new GridBagLayout());
guesses.setBorder(new EmptyBorder(5, 5, 5, 5));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
List<String> options = new ArrayList<>(Arrays.asList(question.getOptions()));
options.add(question.getCorrectAnswer());
Collections.sort(options);
ButtonGroup bg = new ButtonGroup();
for (String option : options) {
JRadioButton btn = new JRadioButton(option);
bg.add(btn);
guesses.add(btn, gbc);
}
add(guesses);
}
public Question getQuestion() {
return question;
}
public class ActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
getQuestion().setUserResponse(e.getActionCommand());
}
}
}
This makes for a nice re-usable component, one which can handle a bunch of questions and not care.
Now, we need some way to manage multiple questions, a quiz!
public class QuizPane extends JPanel {
private List<Question> quiz;
private long timeOut = 5;
private Timer timer;
private JButton next;
private CardLayout cardLayout;
private int currentQuestion;
private JPanel panelOfQuestions;
private Long startTime;
public QuizPane(List<Question> quiz) {
this.quiz = quiz;
cardLayout = new CardLayout();
panelOfQuestions = new JPanel(cardLayout);
JButton start = new JButton("Start");
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
currentQuestion = -1;
nextQuestion();
timer.start();
}
});
JPanel filler = new JPanel(new GridBagLayout());
filler.add(start);
panelOfQuestions.add(filler, "start");
for (int index = 0; index < quiz.size(); index++) {
Question question = quiz.get(index);
QuestionPane pane = new QuestionPane(question);
panelOfQuestions.add(pane, Integer.toString(index));
}
panelOfQuestions.add(new JLabel("The quiz is over"), "last");
currentQuestion = 0;
cardLayout.show(panelOfQuestions, "start");
setLayout(new BorderLayout());
add(panelOfQuestions);
JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
next = new JButton("Next");
buttonPane.add(next);
next.setEnabled(false);
add(buttonPane, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
nextQuestion();
}
});
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = (System.currentTimeMillis() - startTime) / 1000;
if (duration >= timeOut) {
nextQuestion();
} else {
long timeLeft = timeOut - duration;
next.setText("Next (" + timeLeft + ")");
next.repaint();
}
}
});
}
protected void nextQuestion() {
timer.stop();
currentQuestion++;
if (currentQuestion >= quiz.size()) {
cardLayout.show(panelOfQuestions, "last");
next.setEnabled(false);
// You could could loop through all the questions and tally
// the correct answers here
} else {
cardLayout.show(panelOfQuestions, Integer.toString(currentQuestion));
startTime = null;
next.setText("Next");
next.setEnabled(true);
timer.start();
}
}
}
Okay, this is a little more complicated, but the basics are, it manages which question is currently presented to the user, manages the time and allows the user to navigate to the next question if they want to.
Now, it's easy to get lost in the detail...
This part of the code actually set's up the main "view", using a CardLayout
panelOfQuestions.add(filler, "start");
for (int index = 0; index < quiz.size(); index++) {
Question question = quiz.get(index);
QuestionPane pane = new QuestionPane(question);
panelOfQuestions.add(pane, Integer.toString(index));
}
panelOfQuestions.add(new JLabel("The quiz is over"), "last");
currentQuestion = 0;
cardLayout.show(panelOfQuestions, "start");
The start button, "end screen" and each individual QuestionPane are added to the panelOfQuestions, which is managed by a CardLayout, this makes it easy to "flip" the views as required.
I use a simple method to move to the next question
protected void nextQuestion() {
timer.stop();
currentQuestion++;
if (currentQuestion >= quiz.size()) {
cardLayout.show(panelOfQuestions, "last");
next.setEnabled(false);
// You could could loop through all the questions and tally
// the correct answers here
} else {
cardLayout.show(panelOfQuestions, Integer.toString(currentQuestion));
startTime = null;
next.setText("Next");
next.setEnabled(true);
timer.start();
}
}
This basically increments a counter and checks to see if we've run out of questions or not. If we have, it disables the next button and shows the "last" view to the user, if not, it moves to the next question view and restarts the timeout timer.
Now, here comes the "magic"...
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = (System.currentTimeMillis() - startTime) / 1000;
if (duration >= timeOut) {
nextQuestion();
} else {
long timeLeft = timeOut - duration;
next.setText("Next (" + timeLeft + ")");
}
}
});
The Swing Timer acts a pseudo loop, meaning that it will call the actionPerformed method on a regular bases, just like for or while loop would, but it does it in such away that it doesn't block the EDT.
This example adds a little more "magic" in that it acts as a count down timer, it checks how long the question has been visible to the user and presents a count down until it will automatically move to the next question, when the duration is greater then or equal to the timeOut (5 seconds in this example), it calls the nextQuestion method
But how do you use it you ask? You create a List of Questions, create an instance of the QuizPane and add that to some other container which is displayed on the screen, for example...
public class QuizMaster {
public static void main(String[] args) {
new QuizMaster();
}
public QuizMaster() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<Question> quiz = new ArrayList<>(5);
quiz.add(new DefaultQuestion("Bananas are:", "Yellow", "Green", "Blue", "Ping", "Round"));
quiz.add(new DefaultQuestion("1 + 1:", "2", "5", "3", "An artificial construct"));
quiz.add(new DefaultQuestion("In the UK, it is illegal to eat...", "Mince pies on Christmas Day", "Your cousin", "Bananas"));
quiz.add(new DefaultQuestion("If you lift a kangaroo’s tail off the ground...", "It can’t hop", "It will kick you in the face", "Act as a jack"));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new QuizPane(quiz));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
And finally, because I know you'll want one, a fully runable example
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class QuizMaster {
public static void main(String[] args) {
new QuizMaster();
}
public QuizMaster() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<Question> quiz = new ArrayList<>(5);
quiz.add(new DefaultQuestion("Bananas are:", "Yellow", "Green", "Blue", "Ping", "Round"));
quiz.add(new DefaultQuestion("1 + 1:", "2", "5", "3", "An artificial construct"));
quiz.add(new DefaultQuestion("In the UK, it is illegal to eat...", "Mince pies on Christmas Day", "Your cousin", "Bananas"));
quiz.add(new DefaultQuestion("If you lift a kangaroo’s tail off the ground...", "It can’t hop", "It will kick you in the face", "Act as a jack"));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new QuizPane(quiz));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class QuizPane extends JPanel {
private List<Question> quiz;
private long timeOut = 5;
private Timer timer;
private JButton next;
private CardLayout cardLayout;
private int currentQuestion;
private JPanel panelOfQuestions;
private Long startTime;
public QuizPane(List<Question> quiz) {
this.quiz = quiz;
cardLayout = new CardLayout();
panelOfQuestions = new JPanel(cardLayout);
JButton start = new JButton("Start");
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
currentQuestion = -1;
nextQuestion();
timer.start();
}
});
JPanel filler = new JPanel(new GridBagLayout());
filler.add(start);
panelOfQuestions.add(filler, "start");
for (int index = 0; index < quiz.size(); index++) {
Question question = quiz.get(index);
QuestionPane pane = new QuestionPane(question);
panelOfQuestions.add(pane, Integer.toString(index));
}
panelOfQuestions.add(new JLabel("The quiz is over"), "last");
currentQuestion = 0;
cardLayout.show(panelOfQuestions, "start");
setLayout(new BorderLayout());
add(panelOfQuestions);
JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
next = new JButton("Next");
buttonPane.add(next);
next.setEnabled(false);
add(buttonPane, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
nextQuestion();
}
});
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = (System.currentTimeMillis() - startTime) / 1000;
if (duration >= timeOut) {
nextQuestion();
} else {
long timeLeft = timeOut - duration;
next.setText("Next (" + timeLeft + ")");
next.repaint();
}
}
});
}
protected void nextQuestion() {
timer.stop();
currentQuestion++;
if (currentQuestion >= quiz.size()) {
cardLayout.show(panelOfQuestions, "last");
next.setEnabled(false);
// You could could loop through all the questions and tally
// the correct answers here
} else {
cardLayout.show(panelOfQuestions, Integer.toString(currentQuestion));
startTime = null;
next.setText("Next");
next.setEnabled(true);
timer.start();
}
}
}
public interface Question {
public String getPrompt();
public String getCorrectAnswer();
public String[] getOptions();
public String getUserResponse();
public void setUserResponse(String response);
public boolean isCorrect();
}
public class DefaultQuestion implements Question {
private final String prompt;
private final String correctAnswer;
private final String[] options;
private String userResponse;
public DefaultQuestion(String prompt, String correctAnswer, String... options) {
this.prompt = prompt;
this.correctAnswer = correctAnswer;
this.options = options;
}
#Override
public String getPrompt() {
return prompt;
}
#Override
public String getCorrectAnswer() {
return correctAnswer;
}
#Override
public String[] getOptions() {
return options;
}
#Override
public String getUserResponse() {
return userResponse;
}
#Override
public void setUserResponse(String response) {
userResponse = response;
}
#Override
public boolean isCorrect() {
return getCorrectAnswer().equals(getUserResponse());
}
}
public class QuestionPane extends JPanel {
private Question question;
public QuestionPane(Question question) {
this.question = question;
setLayout(new BorderLayout());
JLabel prompt = new JLabel("<html><b>" + question.getPrompt() + "</b></html>");
prompt.setHorizontalAlignment(JLabel.LEFT);
add(prompt, BorderLayout.NORTH);
JPanel guesses = new JPanel(new GridBagLayout());
guesses.setBorder(new EmptyBorder(5, 5, 5, 5));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
List<String> options = new ArrayList<>(Arrays.asList(question.getOptions()));
options.add(question.getCorrectAnswer());
Collections.sort(options);
ButtonGroup bg = new ButtonGroup();
for (String option : options) {
JRadioButton btn = new JRadioButton(option);
bg.add(btn);
guesses.add(btn, gbc);
}
add(guesses);
}
public Question getQuestion() {
return question;
}
public class ActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
getQuestion().setUserResponse(e.getActionCommand());
}
}
}
}
If you used Jframe for this application, just check to see if you added the panel to the frame, you've just added the label to the panel, just check to see if you've added the panel to the Jframe, otherwise it won't show up

How to put timer into a GUI?

I have a GUI with a form for people to fill up and I would like to put a countdown timer at the top right hand corner of the page
Heres the method for the timer to get the remaining time. Say my form class is FillForm and the timer method is found in Timer.
How do I put a dynamic (constantly updating) timer inside of the GUI?
public String getRemainingTime() {
int hours = (int)((this.remainingTime/3600000) % 60);
int minutes = (int)((this.remainingTime/60000) % 60);
int seconds = (int)(((this.remainingTime)/1000) % 60);
return(format.format(hours) + ":" + format.format(minutes)+
":" + format.format(seconds));
}
GUI is built using NetBeans GUI builder.
Try This :
import javax.swing.Timer;
Timer timer=new Timer(1000,new ActionListener(){
public void actionPerformed(ActionEvent e)
{
//code here
}
});
timer.start();
//timer.stop()
Every one Seconds Timer Execute.
Try This Demo :
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
class Counter {
private static int cnt;
static JFrame f;
public static void main(String args[]) {
f=new JFrame();
f.setSize(100,100);
f.setVisible(true);
ActionListener actListner = new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
cnt += 1;
if(cnt%2==0)
{
f.setVisible(true);
}
else
{
f.setVisible(false);
}
}
};
Timer timer = new Timer(500, actListner);
timer.start();
}
}
You should abstract your timer into a UI component. JLabel seems the most suited as it is a text that you want to display.
public class TimerLabel extends JLabel {
// Add in your code for 'format' and 'remainingTime'.
// Note that the first time that 'getText' is called, it's called from the constructor
// if the superclass, so your own class is not fully initialized at this point.
// Hence the 'if (format != null)' check
public TimerLabel() {
Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
}
public String getRemainingTime() {
int hours = (int) ((this.remainingTime / 3600000) % 60);
int minutes = (int) ((this.remainingTime / 60000) % 60);
int seconds = (int) (((this.remainingTime) / 1000) % 60);
return (format.format(hours) + ":" + format.format(minutes) + ":" + format.format(seconds));
}
#Override
public String getText() {
if (format != null) {
return getRemainingTime();
} else {
return "";
}
}
"Could i add this into a Swing.JPanel or something?"
Just put it in the constructor of your form class. Declare the Timer timer; as a class member and not locally scoped so that you can use the start() method like in a button's actionPerformed. Something like
import javax.swing.Timer;
public class GUI extends JFrame {
public Timer timer = null;
public GUI() {
timer = new Timer (500, new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (timerGetsToZero) {
((Timer)e.getSource()).stop();
} else {
timeLabel.setText(getRemainingTime());
}
}
});
}
private void startButtonActionPerformed(ActionEvent e) {
timer.start();
}
}

Categories