简体   繁体   English

Swing Timer的暂停和恢复无法正常工作

[英]Pause and resume for Swing Timer does not work properly

My program is a memory game. 我的程序是一个记忆游戏。 Whenever a tile is clicked, the timer would start. 每当单击磁贴时,计时器就会启动。 I am trying to create a menu for pause and resume of the Swing Timer. 我正在尝试创建一个菜单来暂停和恢复Swing计时器。 The pause works just fine; 暂停工作得很好; however, the problem I am experiencing is whenever I resume the timer after the pause, it would skip four seconds instead of continuing the timer The specific methods for the timer is posted below the whole program code. 但是,我遇到的问题是,只要我在暂停后恢复计时器,就会跳过四秒钟而不是继续运行计时器。计时器的特定方法发布在整个程序代码的下方。 Here's what I currently have: 这是我目前拥有的:

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.*;
import java.util.Collections;
import java.util.Calendar;
import java.time.*;

public class MemoryGame implements ActionListener {

private Timer cdTimer;  //Count down timer of 1.5 secs for unmatched pairs
private Timer swTimer;  //Main timer for the game

private int count = 0;
private long start = Calendar.getInstance().getTimeInMillis();
private long now;
private long elapsed;
boolean match = false;  //Determine if the cdTimer is running or not

private JToggleButton[] buttons;
private JToggleButton first;  //Variable for the first button to match
private JLabel time;  //Label to hold the 

private JMenuItem pause;
private JMenuItem resume;

ArrayList<ImageIcon> iconList = new ArrayList();
ArrayList<JToggleButton> retireButton = new ArrayList();  
ImageIcon icon = new ImageIcon("MemoryGame.png");

public MemoryGame() {

    JFrame jfrm = new JFrame("Memory Game");

    jfrm.setSize(1000, 1000);

    jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    jfrm.setIconImage(icon.getImage());

    time = new JLabel("Elapsed time is 00:00:00");

    GridLayout layout = new GridLayout(3,4);

    JPanel gamePanel = new JPanel();
    gamePanel.setLayout(layout);

    createIcons();  //set icons for the tiles

    buttons = new JToggleButton[12];

    for(int i = 0; i < buttons.length; i++) {
        JToggleButton btn = new JToggleButton(icon);

        buttons[i] = btn;

        buttons[i].addActionListener(this);

        gamePanel.add(buttons[i]);   
    }

    Collections.shuffle(Arrays.asList(buttons));
    Collections.shuffle(iconList);

    jfrm.add(gamePanel, BorderLayout.CENTER);
    time.setHorizontalAlignment(JLabel.CENTER);
    time.setVerticalAlignment(JLabel.CENTER);
    jfrm.add(time, BorderLayout.NORTH);

    //Create menus
    JMenuBar jm = new JMenuBar();
    JMenu action = new JMenu("Action");
    action.setMnemonic(KeyEvent.VK_A);

    JMenu gameTimer = new JMenu("Game Timer");
    gameTimer.setMnemonic(KeyEvent.VK_T);
    pause = new JMenuItem("Pause", KeyEvent.VK_P);
    pause.setAccelerator(KeyStroke.getKeyStroke("control P"));
    pause.setEnabled(false);
    pause.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            elapsed += now;
            swTimer.stop();
        }
    });
    resume = new JMenuItem("Resume", KeyEvent.VK_R);
    resume.setAccelerator(KeyStroke.getKeyStroke("control R"));
    resume.setEnabled(false);
    resume.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            timerContinue();
        }
    });
    gameTimer.add(pause);
    gameTimer.add(resume);
    action.add(gameTimer);

    JMenuItem reveal = new JMenuItem("Reveal", KeyEvent.VK_R);
    reveal.addActionListener(this);
    action.add(reveal);
    action.addSeparator();

    JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X);
    exit.addActionListener(this);
    action.add(exit);

    JMenu help = new JMenu("Help");
    help.setMnemonic(KeyEvent.VK_H);
    JMenuItem viewHelp = new JMenuItem("View Help...", 
KeyEvent.VK_H);
    viewHelp.addActionListener(this);
    help.add(viewHelp);
    help.addSeparator();
    JMenuItem about = new JMenuItem("About", KeyEvent.VK_A);
    about.addActionListener(this);
    help.add(about);

    jm.add(action);
    jm.add(help);
    jfrm.setJMenuBar(jm);

    jfrm.setLocationRelativeTo(null);

    jfrm.setVisible(true);
}

public void actionPerformed(ActionEvent e){
    //this if makes sure the timer does not restart everytime a button is clicked
    if(retireButton.size() == 0){
        timerStart();
    }     

    if(swTimer.isRunning()){
        pause.setEnabled(true);
        resume.setEnabled(true);
    }

    //this if makes sure no button will be input during the 1.5 secs delay
    if(match == false){
        JToggleButton btn = (JToggleButton)e.getSource(); //take in button
        setIcon(btn);  
        resetIcon(btn);
        //this if makes btn equals the first button if it is null
        if(first == null){
            first = btn;
            return;
        }

        matching(first, btn);

        first = null;
    }
}

public void updateTime(){
    long temp = Calendar.getInstance().getTimeInMillis();
    time.setText("Elapsed time is " + formatTime((long) (temp - start)));
    now = temp - start;    
}

public void continueTime(){
    long temp = Calendar.getInstance().getTimeInMillis();
    time.setText("Elapsed time is " + formatTime((long) (temp - start)));
}

private void timerContinue(){
    ActionListener timerAL = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            continueTime();
        }
    };

    //stop the timer if it is still running
    if (swTimer != null && swTimer.isRunning()) {
        swTimer.stop();
    }

    swTimer = new Timer(1000, timerAL);
    swTimer.setInitialDelay(0);
    swTimer.start();
}

public static String formatTime(long ms){
    long millis = ms % 1000;
    long x = ms / 1000;
    long seconds = x % 60;
    x /= 60;
    long minutes = x % 60;
    x /= 60;
    long hours = x % 24;

    return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}

//Method to reset the button to game image when it is clicked for a second time
private void resetIcon(JToggleButton btn){
    if(!btn.isSelected()){
        btn.setIcon(icon);
    }
}

private void timerStart(){
    ActionListener timerAL = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            updateTime();
        }
    };

    //stop the timer if it is still running
    if (swTimer != null && swTimer.isRunning()) {
        swTimer.stop();
    }

    swTimer = new Timer(1000, timerAL);
    swTimer.setInitialDelay(0);
    swTimer.start();
}

private void timerStop(){
    //if all 12 buttons are matched, then stop the timer
    if(retireButton.size() == 12){
        long stop = Calendar.getInstance().getTimeInMillis();
        time.setText("You finished in " + formatTime((long)(stop-start)));
        swTimer.stop();
    }
}

//set the icons for the tiles
private void setIcon(JToggleButton btn) {
    if(btn == buttons[0] || btn == buttons[1])
        btn.setIcon(iconList.get(0)); 

    else if(btn == buttons[2] || btn == buttons[3])
        btn.setIcon(iconList.get(1));

    else if(btn == buttons[4] || btn == buttons[5])
        btn.setIcon(iconList.get(2));

    else if(btn == buttons[6] || btn == buttons[7])
        btn.setIcon(iconList.get(3));

    else if(btn == buttons[8] || btn == buttons[9])
        btn.setIcon(iconList.get(4));

    else if(btn == buttons[10] || btn == buttons[11])
        btn.setIcon(iconList.get(5));
}

//match the two input buttons
private void matching(JToggleButton btn, JToggleButton btn2){
    if(btn.isSelected()){
        if(btn2.isSelected()){
            buttonDisable(btn, btn2); //disable all buttons besides btn, and btn2
            if(!btn.getIcon().toString().equals(btn2.getIcon().toString())){
                startTime(1, btn, btn2);  //start the 1.5 secs countdown
            }
            else {
                retirePair(btn, btn2);
                timerStop();
                buttonEnable(btn, btn2);
            }
        }
    }   
}

private void startTime(int countPassed, JToggleButton btn, JToggleButton btn2){
    ActionListener action = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            if(count == 0){
                cdTimer.stop();
                match = false;  //resets match
                unflipPair(btn, btn2);  //reset tile to game image again
                buttonEnable(btn, btn2);
            }
            else
                count--;
            }

    };
    cdTimer = new Timer(500, action);
    cdTimer.start();
    match = true;
    count = countPassed;
}

//enable buttons other than btn and btn2
private void buttonEnable(JToggleButton btn, JToggleButton btn2){
    for(int i = 0; i < buttons.length; i++){
        if(buttons[i] != btn && buttons[i] != btn2)
            buttons[i].setEnabled(true);
    }
}

//disable buttons other than btn and btn2
private void buttonDisable(JToggleButton btn, JToggleButton btn2){
    for(int i = 0; i < buttons.length; i++){
        if(buttons[i] != btn && buttons[i] != btn2)
            buttons[i].setEnabled(false);
    }
}

private void unflipPair(JToggleButton btn, JToggleButton btn2){
    btn.setIcon(icon);
    btn2.setIcon(icon);
    btn.setEnabled(true);
    btn2.setEnabled(true);
    btn.setSelected(false);
    btn2.setSelected(false);
}

private void retirePair(JToggleButton btn, JToggleButton btn2){
    btn.setSelected(true);
    btn2.setSelected(true);
    btn.removeActionListener(this);
    btn2.removeActionListener(this);
    retireButton.add(btn);
    retireButton.add(btn2);
}

private void createIcons(){
    ImageIcon icon1 = new ImageIcon("1.png");
    ImageIcon icon2 = new ImageIcon("2.png");
    ImageIcon icon3 = new ImageIcon("3.png");
    ImageIcon icon4 = new ImageIcon("4.png");
    ImageIcon icon5 = new ImageIcon("5.png");
    ImageIcon icon6 = new ImageIcon("6.png");

    iconList.add(icon1);
    iconList.add(icon2);
    iconList.add(icon3);
    iconList.add(icon4);
    iconList.add(icon5);
    iconList.add(icon6);
}


public static void main(String args[]) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            if(args.length == 0) //java MemoryGame debug increases the string length to 1
                new MemoryGame();
            else
                new MemoryGame(2);
        }
    });
}

} }

These are the addActionListener: 这些是addActionListener:

pause.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            elapsed += now;
            swTimer.stop();
        }
    });
resume.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e){
            timerContinue();
        }
    });

This is timerContinue method: 这是timerContinue方法:

public void continueTime(){
    long temp = Calendar.getInstance().getTimeInMillis();
    time.setText("Elapsed time is " + formatTime((long) (temp - start)));
}

private void timerContinue(){
    ActionListener timerAL = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            continueTime();
        }
    };

    //stop the timer if it is still running
    if (swTimer != null && swTimer.isRunning()) {
        swTimer.stop();
    }

    swTimer = new Timer(1000, timerAL);
    swTimer.setInitialDelay(0);
    swTimer.start();
}

This is timeStart() method: 这是timeStart()方法:

public void updateTime(){
    long temp = Calendar.getInstance().getTimeInMillis();
    time.setText("Elapsed time is " + formatTime((long) (temp - start)));
    now = temp - start;    
}

private void timerStart(){
    ActionListener timerAL = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            updateTime();
        }
    };

    //stop the timer if it is still running
    if (swTimer != null && swTimer.isRunning()) {
        swTimer.stop();
    }

    swTimer = new Timer(1000, timerAL);
    swTimer.setInitialDelay(0);
    swTimer.start();
}

So, you capture start 因此,您捕获了start

private long start = Calendar.getInstance().getTimeInMillis();

when the class is first instantiated ... not sure this is a good idea, but lets flow with it... 当第一次实例化该类时...不确定这是一个好主意,但让它顺其自然...

When you pause the timer... 当您暂停计时器...

pause.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e){
        elapsed += now;
        swTimer.stop();
    }
});

you capture the elapsed time 您捕获elapsed时间

When you resume the clock... 当您恢复时钟时...

public void continueTime(){
    long temp = Calendar.getInstance().getTimeInMillis();
    time.setText("Elapsed time is " + formatTime((long) (temp - start)));
}

private void timerContinue(){
    ActionListener timerAL = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            continueTime();
        }
    };

    //stop the timer if it is still running
    if (swTimer != null && swTimer.isRunning()) {
        swTimer.stop();
    }

    swTimer = new Timer(1000, timerAL);
    swTimer.setInitialDelay(0);
    swTimer.start();
}

You're using the current time and subtracting start , but start has never been reset, so it's still calculating from the time it was first initialised. 您正在使用当前时间并减去start ,但是start从未被重置,因此它仍从首次初始化的时间开始计算。

Conceptually, a "pause-able" clock is one which has two important states. 从概念上讲,“可暂停”时钟是具有两个重要状态的时钟。

  • The "current" run time. “当前”运行时间。 That is the time from when it was started/resumed to now 从开始/恢复到现在的时间
  • The "previous" run time. “上一个”运行时间。 That is the total amount of time that the timer has been allowed to run. 那是计时器被允许运行的总时间。

So. 所以。 When you pause the clock, you need to do a couple of things... 当您暂停时钟时,您需要做几件事...

  1. Calculate the total running time for this phase - the time from when it was started to now. 计算此阶段的总运行时间-从开始到现在的时间。
  2. Add that to an overall "running time", which keeps track of the total time the clock has been allowed to run. 将其添加到总的“运行时间”,以跟踪允许时钟运行的总时间。
  3. Reset the start time (preferably to something like null , so it's obvious) 重置开始时间(最好设置为null ,这很明显)

When resumed, you need to... 恢复后,您需要...

  1. Store the current time, as the start time. 存储当前时间作为start时间。
  2. Calculate the time between the start time and now (when the clock ticks) 计算从start时间到现在(时钟滴答声)之间的时间
  3. Add the totalRunningTime to it, which creates the overall running time for the clock. totalRunningTime添加totalRunningTime ,以创建时钟的总体运行时间。

Simple 😁 简单😁

Firstly, the question itself is not that uncommon, and a number solutions are available if you spend some time researching the problem. 首先,问题本身并不少见,如果您花一些时间研究问题,则可以找到许多解决方案。

Having said that, since it's now 2018 and Java 11 is visible on the horizon, you really should be making use of Java's newer date/time API, which provides a number of really useful APIs, including Duration and the ability to treat time independently of the underlying calendar system ... so we don't get daylight savings oddities 😝 话虽如此,由于现在已经是2018年,并且即将出现Java 11,因此您确实应该使用Java的更新日期/时间API,它提供了许多非常有用的API,包括Duration和独立于时间的处理能力。基本的日历系统...所以我们不会得到夏时制的奇怪之处😝

The following is an example of some library code I use, which implements the functionality from above. 以下是我使用的一些库代码示例,该代码从上面实现了功能。

The "strange" thing about it is, it's independent of a timing system. 它的“奇怪”之处在于,它独立于计时系统。 That is, it doesn't "tick". 也就是说,它不会“打勾”。 Instead, you'd use a Swing Timer (in your case) to call getDuration and update the UI as required. 相反,您将使用Swing Timer (在您的情况下)调用getDuration并根据需要更新UI。

public class StopWatch {

    private Instant startTime;
    private Duration totalRunTime = Duration.ZERO;

    public StopWatch start() {
        startTime = Instant.now();
        return this;
    }

    public StopWatch stop() {
        Duration runTime = Duration.between(startTime, Instant.now());
        totalRunTime = totalRunTime.plus(runTime);
        startTime = null;
        return this;
    }

    public StopWatch pause() {
        return stop();
    }

    public StopWatch resume() {
        return start();
    }

    public StopWatch reset() {
        stop();
        totalRunTime = Duration.ZERO;
        return this;
    }

    public boolean isRunning() {
        return startTime != null;
    }

    public Duration getDuration() {
        Duration currentDuration = Duration.ZERO;
        currentDuration = currentDuration.plus(totalRunTime);
        if (isRunning()) {
            Duration runTime = Duration.between(startTime, LocalDateTime.now());
            currentDuration = currentDuration.plus(runTime);
        }
        return currentDuration;
    }
}

And if you want to see the concept in action, you could have a look at Adding resume function to stopwatch 如果您想了解实际的概念,可以看看将恢复功能添加到秒表中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM