Sam Palmer
Sam Palmer

Reputation: 1725

Pausing a timerTask

I have hit another wall. After getting my key input working, I have been racking my brains for hours, i want to create a pause function, so that if the same key is pressed again the timertask stops running (i.e the game is paused)

JPanel component = (JPanel)frame.getContentPane();
    component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
    component.getActionMap().put("space", (new AbstractAction(){
        public void actionPerformed(ActionEvent e){

            Timer timer = new Timer();

            timer.scheduleAtFixedRate(new TimerTask(){
            public void run(){
                    grid.stepGame();
                }
            },250, 250);



        }}));


        }

The problem is i cant use a global boolean isRunning var and switch it each time the key is pressed because the timerTask method in a nested class (so the boolean isRunning would have to be declared final to be accessed...). Any ideas on how to detect if the key is pressed again or if the game is already running so i can pause/cancel my timerTask.

Many Thanks Sam

Upvotes: 2

Views: 2698

Answers (5)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285440

Since this is a Swing game, you should be using a javax.swing.Timer or Swing Timer and not a java.util.Timer. By using a Swing Timer, you guarantee that the code being called intermittently is called on the EDT, a key issue for Swing apps, and it also has a stop method that pauses the Timer. You can also give your anonymous AbstractAction class a private boolean field to check if the key is being pressed for the first time or not.

Also, kudos and 1+ for using Key Bindings instead of a KeyListener.

e.g.,

  JPanel component = (JPanel) frame.getContentPane();
  component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
  component.getActionMap().put("space", (new AbstractAction() {
     private boolean firstPress = true;
     private int timerDelay = 250;
     private javax.swing.Timer keyTimer = new javax.swing.Timer(timerDelay , new ActionListener() {

        // Swing Timer's actionPerformed
        public void actionPerformed(ActionEvent e) {
           grid.stepGame();
        }
     });

     // key binding AbstractAction's actionPerformed
     public void actionPerformed(ActionEvent e) {
        if (firstPress) {
           keyTimer.start();
        } else {
           keyTimer.stop();
        }

        firstPress = !firstPress;
     }
  }));

Another useful option is to perform a repeating task on key press and stop it on key release, and this can be done easily by getting the keystrokes for on press and on release:

KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true) // for key release
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false) // for key press

For example:

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

public class SwingTimerEg2 {
   private JFrame frame;
   private Grid2 grid = new Grid2(this);
   private JTextArea textarea = new JTextArea(20, 20);
   private int stepCount = 0;

   public SwingTimerEg2() {
      frame = new JFrame();

      textarea.setEditable(false);
      frame.add(new JScrollPane(textarea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));

      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
      setUpKeyBinding();
   }

   void setUpKeyBinding() {
      final int timerDelay = 250;
      final Timer keyTimer = new Timer(timerDelay, new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent e) {
            grid.stepGame();
         }
      });
      JPanel component = (JPanel) frame.getContentPane();
      final int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
      final String spaceDown = "space down";
      final String spaceUp = "space up";
      component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), spaceDown);
      component.getActionMap().put(spaceDown, (new AbstractAction() {
         public void actionPerformed(ActionEvent e) {
            keyTimer.start();
         }
      }));
      component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), spaceUp);
      component.getActionMap().put(spaceUp, (new AbstractAction() {
         public void actionPerformed(ActionEvent e) {
            keyTimer.stop();
         }
      }));

   }

   public void doSomething() {
      textarea.append(String.format("Zap %d!!!%n", stepCount));
      stepCount ++;
   }

   private static void createAndShowGui() {
      new SwingTimerEg2();
   }

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

}

class Grid2 {
   private SwingTimerEg2 stEg;

   public Grid2(SwingTimerEg2 stEg) {
      this.stEg = stEg;
   }

   void stepGame() {
      stEg.doSomething();
   }
}

Upvotes: 6

Brian Coleman
Brian Coleman

Reputation: 1070

Keep a reference to the Timer somewhere, say in your game class. When the game is paused cancel the Timer. This will cancel any currently scheduled tasks. Then when the game is unpaused schedule the timer again as you have done above.

public class Game {

    private Timer timer;

    public void pause() {
        if (timer != null) {
            timer.pause();
        }
    }

    public void startOrResumeGame() {
        if (timer == null) {
            timer = new Timer();
        } else {
            // Just in case the game was already running.
            timer.cancel();
        }
        timer.scheduleAtFixedRate(new TimerTask() {
            public void run() {
                    grid.stepGame();
                }
            }, 250, 250);

    }
}

Upvotes: 0

Voo
Voo

Reputation: 30245

No you got the wrong idea about WHY you need final for anonymous classes! Final is only needed for local variables (well more exactly any variable that might have a live time shorter than the given object).

Hence a static variable in a class is perfectly fine and will work perfectly!

Edit: example:

public class Main {
    interface Test {
        void call();
    }

    public static volatile boolean running = true;

    public static void main(String[] args) {
        Test t = new Test() {
            @Override
            public void call() {
                System.out.println(Main.running);
            }
        };
        t.call();
        running = false;
        t.call();
    }
}

Upvotes: 0

mah
mah

Reputation: 39847

The final qualifier requirement can easily be avoided -- replace your inner method (which has the final requirement) with a call to a class method.

Upvotes: 0

alf
alf

Reputation: 8523

Easiest and dirty solution:

final boolean[] isRunning = new boolean[1];

You don't want to do that—but it works assuming proper synchronization around.

What would be better is

final AtomicBoolean isRunning = new AtomicBoolean();

What would be even better is to review the design once again: global state usually means, "global problems"

Upvotes: 0

Related Questions