Heronimus Lex Heronimus Lex - 1 month ago 7
Java Question

Program counts down by 2 instead of by 1 and I can't see any logic errors

I have recently learned how to use the timer java class and have created a program which counts down using a timer and displays this countdown using a label. My issue is that the program counts down by two instead of 1.

package test;

import java.awt.*;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.Timer;
import static javax.swing.UIManager.get;

public class Test extends JFrame implements ActionListener{

//Declare Global Variable
int timerValGlobal = 25;

//Define components to add
JButton startTimer = new JButton("Start Timer");
JButton exitButton = new JButton("Exit");
JLabel time = new JLabel();
Timer timer = new Timer(1000, this);


//Define panels
JPanel pane = new JPanel();
JPanel exitPane = new JPanel();



public Test(){
setExtendedState(JFrame.MAXIMIZED_BOTH);
setUndecorated(true);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);

//Add buttons to panels
pane.add(startTimer);
pane.add(time);
exitPane.add(exitButton);

//Add panels to form;
add(pane, BorderLayout.NORTH);
add(exitPane, BorderLayout.SOUTH);

//Add ActionListeners to Buttons
startTimer.addActionListener(this);
exitButton.addActionListener(this);
timer.addActionListener(this);


}



public static void main(String[] args) {
Test t = new Test();
}


public void actionPerformed(ActionEvent evt){
Object source = evt.getSource();
if(source == exitButton){
System.exit(0);
}

else if(source == startTimer){
timer.start();
}

else if (source == timer){
time.setText(String.valueOf(timerValGlobal));
timerValGlobal = timerValGlobal - 1;

if(timerValGlobal == 0){
timer.stop();
timerValGlobal = 25;
}
}
}
}

Answer

This is certainly hard bug to notice! (at least for me :D)

When you create the timer, you did this, right?

Timer timer = new Timer(1000, this);

Why do you write the word this in that piece of code? You're saying that this should be used as an action listener for the timer so that each second, one of the methods in the class will be called. Right?

Now, here's where it goes wrong. In the constructor, you set this as the action listener again!

public Test(){
    setExtendedState(JFrame.MAXIMIZED_BOTH);
    setUndecorated(true);
    setVisible(true);
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    //Add buttons to panels
    pane.add(startTimer);
    pane.add(time);
    exitPane.add(exitButton);

    //Add panels to form;
    add(pane, BorderLayout.NORTH);
    add(exitPane, BorderLayout.SOUTH);

    //Add ActionListeners to Buttons
    startTimer.addActionListener(this);
    exitButton.addActionListener(this);
    // LOOK! THIS LINE!
    // VVVVVVVVVVVVVVV
    timer.addActionListener(this);
}

You even wrote comments that says the following block of code is used to add action listeners to the buttons!

Because of the two identical action listeners, the actionPerformed method is called twice a second, hence the output!

Just remove the line that adds a second action listener!

Side note, you should create your frame on the Event Queue like this:

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            new Test();
        }
    });
}

Or this, in Java 8:

public static void main(String[] args) {
    EventQueue.invokeLater(Test::new);
}