计时器问题 - 更新显示

时间:2017-04-08 02:15:23

标签: java swing timer

自从上一个问题以来,我的计时器程序变得更加复杂,而且我遇到了另一面墙。 我似乎无法更新两个计时器的显示。他们使用println打印得很好但是如果我使用静态变量,它会给出正确的值,但仅限于Break计时器,说得通。所以,无论我做什么,我似乎无法弄清楚如何让Active计时器在应该的时候进行更新,而Break计时器应该更新。这是相关的我的代码的一部分。

您可以提供有关如何在计时器递增时让显示更新的指导吗?

此类是计时器的功能:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class TaskTimer {

    private int seconds = 0;
    private int minutes = 0;
    private int hours = 0;
    private final int UNIT = 1000;
    private Timer timer;
    private String name;


    public TaskTimer(String name) {

        ActionListener go = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println(count());

            }   
        };

        timer = new Timer(UNIT, go);    
    }


    public void begin() {
        if(ControlTimer.isStopped()) {
            seconds = 0;
            minutes = 0;
            hours = 0;

        }
        timer.start();  
    }

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

    public String count() {
        if(seconds < 59) {
            seconds++;
        } else if(minutes < 59) {
            seconds = 0;
            minutes++;
        } else {
            seconds = 0;
            minutes = 0;
            hours++;
        }

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

    public int getSeconds() {
        return seconds;
    }

    public void setSeconds(int s) {
        seconds = s;
    }

    public int getMinutes() {
        return minutes;
    }

    public void setMinutes(int m) {
        minutes = m;
    }

    public int getHours() {
        return hours;
    }

    public void setHours(int h) {
        hours = h;
    }

}

这个创建面板进入窗口:

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;

public class TaskTimerPanel extends JPanel{

    String panelName;
    private static String timerValue;
    private JLabel timeDisplay;
    JLabel title;
    TaskTimer taskTimer;

    public TaskTimerPanel(String panelName) {

        // Get timer
        taskTimer = new TaskTimer(panelName);

        // Setup Panel
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));

        // Add Title Label
        this.panelName = panelName;
        title = new JLabel(panelName);
        p.add(title);

        // Add Timer Display
        timerValue = "00:00:00";
        timeDisplay = new JLabel(timerValue);
        p.add(timeDisplay);

        // Add Formatting
        title.setAlignmentX(Component.CENTER_ALIGNMENT);
        timeDisplay.setAlignmentX(CENTER_ALIGNMENT);
        timeDisplay.setOpaque(true);
        title.setBorder(new EmptyBorder(10, 10, 10, 10));
        timeDisplay.setBorder(BorderFactory.createCompoundBorder(new EtchedBorder(), 
            new EmptyBorder(10, 10, 10, 10)));
        timeDisplay.setBackground(Color.WHITE);
        Font font = new Font("Arial", Font.PLAIN, 48);
        title.setFont(font);
        timeDisplay.setFont(font);
        p.setBorder(BorderFactory.createCompoundBorder(new EtchedBorder(),
             new EmptyBorder(10, 10, 10, 10)));

        // Add to JFrame
        add(p);
    }
    public JLabel getText() {
        return timeDisplay;
    }

    public void changeDisplay(String time) {
        getText().setText(time);
    }
}

控制器:

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

public class ControlTimer {
    private static boolean stopped = true;
    private TaskTimerWindow t;

    public static boolean isStopped() {
        return stopped;
    }

    public void activeTime() {
        t.activeTimer.taskTimer.begin();
        t.breakTimer.taskTimer.end();
        stopped = false;
    }

    public void breakTime() {
        if(!stopped) {
            t.activeTimer.taskTimer.end();
            t.breakTimer.taskTimer.begin();
        }
    }

    public void reset() {
        if(!stopped) {
            stopped = true;
            t.activeTimer.taskTimer.end();
            t.breakTimer.taskTimer.end();
        }
    }


    public ControlTimer() {
        t = new TaskTimerWindow();

        t.buttons.start.addActionListener(new ActionListener() {    
            public void actionPerformed(ActionEvent e) {
                activeTime();
            }
        });

        t.buttons.stop.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                reset();
            }
        });

        t.buttons.pause.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                    breakTime();
            }
        });
    }

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

我省略了窗口设置和按钮类,因为我不认为它们对这个问题非常重要。

2 个答案:

答案 0 :(得分:3)

让我们退后一步,你似乎需要......

  • “计时器”,具有固定的持续时间,需要生成某种“滴答”事件,让观察者知道它已更新
  • “定时器”的某种“列表”,可以在每个“定时器”完成时按顺序运行它们

你要小心的是,没有将对象的引用传递给其他对象,这些对象实际上没有责任改变它们或对它们感兴趣。相反,您应该考虑使用某种Observer Pattern,它允许您以分离的方式监视对象的更改。

这是"single responsibility principle"

在这种情况下,Timer并不关心你的用户界面,它关心的是在某个时间间隔内运行指定的时间段,在“滴答”时生成事件以及何时完成,就是它。更新UI或做出任何其他决定并不是它的责任。

当你遇到问题时,你应该花一些时间将问题分解成可管理的块 - AKA自上而下分解。这使您可以识别问题的各个方面,谁联系在一起以及您可能需要共享的信息

具有时间限制的任务......

根据我对您的问题的理解,您有一些任务具有已定义的持续时间,并且必须提供何时通知

  • 它滴答
  • 完成

对我来说,我总是从一些接口开始,这些接口定义了我想向外界展示的整体合同/期望(AKA代码到接口,而不是实现)

public interface TimedTask {
    public void addTimedTaskListener(TimedTaskListener listener);
    public void removeTimedTaskListener(TimedTaskListener listener);

    public void addTimedTaskTickListener(TimedTaskTickListener listener);
    public void removeTimedTaskTickListener(TimedTaskTickListener listener);

    public void start();
    public void stop();
    public void reset();
    public boolean isRunning();

    public Duration getRunDuration();
    public Duration getTotalDuration();
}

public class TimedTaskEvent extends EventObject {

    public enum State {
        RUNNING, PAUSED, COMPLETED
    }

    private State state;

    public TimedTaskEvent(Object source, State state) {
        super(source);
        this.state = state;
    }

    public State getState() {
        return state;
    }

}

public interface TimedTaskListener extends EventListener {
    public void taskStateChanged(TimedTaskEvent evt);
}

public interface TimedTaskTickListener extends EventListener {
    public void timedTaskTicked(TimedTaskEvent evt);
}

好的,那里有一点点。 TimedTask是一项具有“持续时间”(getRunDuration)和总运行时间(getTotalDuration)或任务运行时间的任务。

它提供了两个观察者,一个状态被改变以监视生命周期的变化,一个勾选的通知让观察者知道计时器何时更新了。

它还提供了一些简单的管理功能,startstopreset。以下实现允许任务“暂停”和“恢复”(通过调用stopstart),但这只是因为我有代码来执行此操作:P

通常,我会从abstract实现开始并实现最常见的功能(在此,可能是事件管理),这允许将来的实现专注于他们自己的实现的重要方面,但是为了简洁起见,我直接进入默认实现

public class DefaultTimedTask implements TimedTask {

    private EventListenerList listenerList = new EventListenerList();

    private Timer timer;

    // How long the timer has been continuosuly running     
    private Duration totalDuration;
    // The time of the last tick
    private LocalDateTime tickTime;

    // How long the timer should run for
    private Duration runDuration;

    public DefaultTimedTask(Duration duration, int tickInterval) {
        runDuration = duration;
        totalDuration = Duration.ZERO;
        timer = new Timer(tickInterval, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (tickTime == null) {
                        tickTime = LocalDateTime.now();
                    }
                    LocalDateTime now = LocalDateTime.now();
                    Duration tickDuration = Duration.between(tickTime, now);
                    totalDuration = totalDuration.plus(tickDuration);

                    fireTaskTimerTicked();

                    if (totalDuration.compareTo(runDuration) >= 0) {
                        timer.stop();
                        fireTaskTimerStartedChanged(TimedTaskEvent.State.COMPLETED);
                    }
                }
            });
    }

    @Override
    public void reset() {
        timer.stop();
        tickTime = null;
        totalDuration = Duration.ZERO;
    }

    public void setPaused(boolean paused) {
        if (paused && timer.isRunning()) {
            timer.stop();
            fireTaskTimerStartedChanged(TimedTaskEvent.State.PAUSED);
        } else if (!paused && !timer.isRunning()) {
            tickTime = null;
            timer.start();
            fireTaskTimerStartedChanged(TimedTaskEvent.State.RUNNING);
        }
    }

    protected void fireTaskTimerTicked() {
        TimedTaskTickListener[] listeners = listenerList.getListeners(TimedTaskTickListener.class);
        if (listeners.length > 0) {
            TimedTaskEvent evt = new TimedTaskEvent(this, TimedTaskEvent.State.RUNNING);
            for (TimedTaskTickListener listener : listeners) {
                listener.timedTaskTicked(evt);
            }
        }
    }

    protected void fireTaskTimerStartedChanged(TimedTaskEvent.State state) {
        TimedTaskListener[] listeners = listenerList.getListeners(TimedTaskListener.class);
        if (listeners.length > 0) {
            TimedTaskEvent evt = new TimedTaskEvent(this, state);
            for (TimedTaskListener listener : listeners) {
                listener.taskStateChanged(evt);
            }
        }
    }

    public boolean isPaused() {
        return !timer.isRunning();
    }

    @Override
    public void addTimedTaskListener(TimedTaskListener listener) {
        listenerList.add(TimedTaskListener.class, listener);
    }

    @Override
    public void removeTimedTaskListener(TimedTaskListener listener) {
        listenerList.remove(TimedTaskListener.class, listener);
    }

    @Override
    public void addTimedTaskTickListener(TimedTaskTickListener listener) {
        listenerList.add(TimedTaskTickListener.class, listener);
    }

    @Override
    public void removeTimedTaskTickListener(TimedTaskTickListener listener) {
        listenerList.remove(TimedTaskTickListener.class, listener);
    }

    @Override
    public void start() {
        setPaused(false);
    }

    @Override
    public void stop() {
        setPaused(true);
    }

    @Override
    public boolean isRunning() {
        return timer.isRunning();
    }

    @Override
    public Duration getTotalDuration() {
        return totalDuration;
    }

    @Override
    public Duration getRunDuration() {
        return runDuration;
    }

}

好的,再说一次,还有很多事情要做。此实现依赖于Java 8的新Time API来跟踪计时器的运行时间。这个实现不是假设Timer是准确的(因为它不是),而是跟踪每个节拍之间的时间量,然后将其添加到Duration,表示“{1}}的”总运行时间“。任务。这样可以简单地暂停和重新启动实现。

一旦计时器运行至少所需的持续时间,它将生成COMPLETED状态更改并停止。

管理它的一些方法

好的,这让我们有了一个起点。接下来的问题是,你有许多定时任务需要按顺序运行,一个接一个。现在你可以简单地设置链接在一起的实现,但这会给定时器增加额外的责任,相反,我选择创建一个“管理器”,它可以控制定时器,监控它们的状态,当一个完成时,启动下一个...

public class TaskList implements TimedTaskListener {

    private List<TimedTask> timers = new ArrayList<>(25);
    private boolean running = false;
    private TimedTask current;

    public void add(TimedTask timer) {
        timer.addTimedTaskListener(this);
        timers.add(timer);
    }

    public void remove(TimedTask timer) {
        timer.removeTimedTaskListener(this);
        timers.remove(timer);
    }

    public boolean isRunning() {
        return running;
    }

    public void start() {
        if (current != null) {
            current.start();
            running = true;
        } else {
            startNext();
        }
    }

    public void stop() {
        if (current != null) {
            current.stop();
        }
        running = false;
    }

    protected void startNext() {
        if (!timers.isEmpty()) {
            current = timers.remove(0);
            current.start();
            running = true;
        } else {
            running = false;
        }
    }

    @Override
    public void taskStateChanged(TimedTaskEvent evt) {
        switch (evt.getState()) {
            case COMPLETED:
                TimedTask timer = (TimedTask) evt.getSource();
                remove(timer);
                startNext();
                break;
        }
    }
}

真的,没什么特别的

把它放在一起......

嗯,这一切都很好,但你会怎么用呢?

基本上,你会创建一个TaskList,你可以根据需要设置TimedTask个实例,确保注册为TimedTaskListener以获得更新,这样你就可以更新用户界面(或者你需要做的其他事情),将它们添加到TaskList,当你准备就绪时,启动它,例如......

import java.awt.EventQueue;
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.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.EventListenerList;

public class TimerTest {

    public static void main(String[] args) {
        new TimerTest();
    }

    public TimerTest() {
        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 JLabel label = new JLabel("00:00:00.00");

        public TestPane() {
            setLayout(new GridBagLayout());
            add(label);
            DefaultTimedTask task = new DefaultTimedTask(Duration.ofMinutes(3), 100);
            task.addTimedTaskTickListener(new TimedTaskTickListener() {
                @Override
                public void timedTaskTicked(TimedTaskEvent evt) {
                    TimedTask task = (TimedTask) evt.getSource();
                    Duration duration = task.getTotalDuration();
                    label.setText(format(duration));
                }
            });

            TaskList taskList = new TaskList();
            taskList.add(task);
            taskList.start();
        }

        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();
            double seconds = millis / 1000.0;

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

其他事情......

这是一个非常简单的概念概述,您可能还希望为TaskList提供某种观察者,它可以在完成所有任务时生成通知。

您还需要为TimedTask interface添加一些类型的标识符,例如name或其他您可以使用的标识符,这样您才能知道实际发生了什么计时器实际上正在运行

答案 1 :(得分:2)

我过去没有回答过任何问题作为公平警告。我试图为你想出一个很好的解决方案,但措辞可能不是很好。我希望这可能会阻止你,直到更好的合格Stacker回复这里:

我能想到的最佳解决方案是使用java中的组件库。我假设这个显示计时器不是JDK的一部分,而是你从头开始创建的东西。因为我没有花时间来运行你的代码,因为我认为如果没有你省略的文件,这将毫无意义,这可能是一个不错的解决方案。使用Component对象处理要打印的String,所有需要实现的是JFrame类中的另一个计时器,用于处理Component调用repaint方法的频率。 祝你好运,我希望这至少是一个有用的推动。干杯

//Class Variable
private static YourTimerComponent timerComponent = new YourTimerComponent(); 

//Put everything below where you made the JFrame
static int delay = 1000; //Milliseconds

frame.add(timerComponent);

ActionListener refresh  = new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        timerComponent.repaint();
    }
};
new Timer(delay, refresh).start();

然后,您需要创建绘制字符串的组件。您还应该决定在JFrame类中的JFrame上显示它的位置。