自从上一个问题以来,我的计时器程序变得更加复杂,而且我遇到了另一面墙。 我似乎无法更新两个计时器的显示。他们使用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();
}
}
我省略了窗口设置和按钮类,因为我不认为它们对这个问题非常重要。
答案 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
)或任务运行时间的任务。
它提供了两个观察者,一个状态被改变以监视生命周期的变化,一个勾选的通知让观察者知道计时器何时更新了。
它还提供了一些简单的管理功能,start
,stop
和reset
。以下实现允许任务“暂停”和“恢复”(通过调用stop
和start
),但这只是因为我有代码来执行此操作: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上显示它的位置。