shakel
shakel

Reputation: 217

Two JButtons using the same actionListener to Start/Stop Timer

I'm fairly new to Java and could do with some help. I trying to make a Timer countdown from a set time to 0. I have this functionality working fine, but I want to add functionality to allow me to stop the Timer as it's counting down.

Here is my code (I'm trying to achieve this using MVC)

This is the control part:

import java.awt.event.*;
import javax.swing.*;

public class StartButton extends JButton implements ActionListener
{
    private TimerModel model;
    private Timer timer;
    private boolean isStarted;

    public StartButton(String buttonText, TimerModel model)
    {
        super(buttonText);
        addActionListener(this);
        this.model = model;
        isStarted = false;
    }

    public void actionPerformed(ActionEvent evt)
    {
        if(!isStarted) 
        {
            timer = new Timer(1000, this);
            timer.start();
            isStarted = true;
        }

        model.timerCountdown();
    }

    public void stopTimer()
    {
        timer.stop();
    }
}

I have looked at some other similar issues online and I tried this within the constructor (Note: I didn't use implements ActionListener, and removed the actionPerformed method I had above):

if(buttonText.equals("Start"))
    {
        addActionListener(new ActionListener() 
        {
            public void actionPerformed(ActionEvent e)
            {
               if(!isStarted) 
               {
                   timer = new Timer(1000, this);
                   timer.start();
                   isStarted = true;
               }

               model.timerCountdown(); 
            }
        });
    }

    if(buttonText.equals("Stop"))
    {
        addActionListener(new ActionListener() 
        {
            public void actionPerformed(ActionEvent e)
            {
               timer.stop(); 
            }
        });
    }

Now this part handles the counting down ok, but when I click the stop button it shows an exception(See stack trace here), and it continues to count down.

I have limited knowledge, but I guess it's something to do with the way I'm trying to stop the Timer.

I would appreciate it if someone could point me in the right direction, or at least explain to me why this happens.

Upvotes: 1

Views: 2073

Answers (2)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285401

Again, if you're not changing the basic behavior of the JButton itself, such as how it is drawn, but instead are only changing the button's title and behavior when pressed, then don't extend JButton. Instead give each button its own Action, an object from a class that extends from AbstractAction. Consider these guys as similar to ActionListeners on steroids. They have the same abilities as ActionListeners and then some since they can easily change the button's title, whether it is enabled or not, its mnemonic, icon,...

For example:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.*;

import javax.swing.*;

@SuppressWarnings("serial")
public class MyTimerGui {
   private static final String TITLE = "Flashing Label";
   private static final int TIMER_DELAY = 200;
   private static final int GAP = 3;
   private static final float LABEL_POINTS = 32F;
   private JPanel mainPanel = new JPanel();
   private JLabel flashyLabel = new JLabel(TITLE, SwingConstants.CENTER);
   private Timer timer = new Timer(TIMER_DELAY, new TimerListener());

   public MyTimerGui() {
      Font font = flashyLabel.getFont();
      font = font.deriveFont(LABEL_POINTS);
      flashyLabel.setFont(font);
      flashyLabel.setOpaque(true);

      JPanel buttonPanel = new JPanel(new GridLayout(1, 0, GAP, 0));
      buttonPanel.add(new JButton(new StartAction(this, "Start", KeyEvent.VK_S)));
      buttonPanel.add(new JButton(new StopAction(this, "Stop", KeyEvent.VK_T)));
      buttonPanel.add(new JButton(new ExitAction(this, "Exit", KeyEvent.VK_X)));

      mainPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
      mainPanel.setLayout(new BorderLayout());
      mainPanel.add(flashyLabel, BorderLayout.CENTER);
      mainPanel.add(buttonPanel, BorderLayout.PAGE_END);
   }

   public JComponent getMainComponent() {
      return mainPanel;
   }

   public void start() {
      timer.start();
   }

   public void stop() {
      timer.stop();
      flashyLabel.setForeground(null);
      flashyLabel.setBackground(null);
   }

   public void exit() {
      timer.stop();
      Window win = SwingUtilities.getWindowAncestor(mainPanel);
      win.dispose();
   }

   private class TimerListener implements ActionListener {
      private final Color foreground1 = Color.green;
      private final Color background1 = Color.red;

      @Override
      public void actionPerformed(ActionEvent aEvt) {
         Color fg = flashyLabel.getForeground();
         if (foreground1.equals(fg)) {
            flashyLabel.setForeground(null);
            flashyLabel.setBackground(null);
         } else {
            flashyLabel.setForeground(foreground1);
            flashyLabel.setBackground(background1);
         }
      }
   }

   private class StartAction extends AbstractAction {
      private MyTimerGui myTimerGui;

      public StartAction(MyTimerGui myTimerGui, String name, int mnemonic) {
         super(name);
         putValue(MNEMONIC_KEY, mnemonic);
         this.myTimerGui = myTimerGui;
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         myTimerGui.start();
      }
   }

   private class StopAction extends AbstractAction {
      private MyTimerGui myTimerGui;

      public StopAction(MyTimerGui myTimerGui, String name, int mnemonic) {
         super(name);
         putValue(MNEMONIC_KEY, mnemonic);
         this.myTimerGui = myTimerGui;
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         myTimerGui.stop();
      }
   }

   private class ExitAction extends AbstractAction {
      private MyTimerGui myTimerGui;

      public ExitAction(MyTimerGui myTimerGui, String name, int mnemonic) {
         super(name);
         putValue(MNEMONIC_KEY, mnemonic);
         this.myTimerGui = myTimerGui;
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         myTimerGui.exit();
      }
   }

   private static void createAndShowGui() {
      MyTimerGui myTimerGui = new MyTimerGui();

      JFrame frame = new JFrame("MyTimer");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(myTimerGui.getMainComponent());
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

Upvotes: 3

James Taylor
James Taylor

Reputation: 6258

I agree with the comments that say you shouldn't be extending a JButton. Maybe the logic should be carried out in the main class of your application, the same class that class that deals with the creation and storage of the components.

But I digress. To answer your question, I think there are really two ways to approach this. Either (A) have the actionListener stored within your class as you did in your code or (B) write an actionListener outside of the object itself.

Was the constructor you tried to implement this in the main class constructor?

I think you need something like the following (this, again, is in the main class):

StartButton start = new JButton("Start");
StopButton stop = new JButton("Stop");

start.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        // called when the button is pressed
        buttonPressed();
    }
});

stop.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        // called when the button is pressed
        buttonPressed();
    }
});

Then you would write this method in the same class:

private void buttonPressed() {
    System.out.println("Button pressed!");
}

I just whipped up a quick test of this, so I can confirm that this method works.

PS: I would also suggest having the button contain a boolean state instead of checking for the text of the button, if you do intend to keep using the StartButton and associated classes.

Upvotes: 2

Related Questions