我试图在Javafx中使用GUI和Timer类创建一个简单的计时器。当按下开始按钮时,它应该从输入时间倒计时并在达到0时停止。
我有剩余的时间(以毫秒为单位)在GUI上的TextField中更新,但它只运行一个随机数毫秒,通常在100-200之间,然后冻结并抛出非常多的异常。
我试图查明错误的来源,并发现还存在并发修改异常。
这是我的Timer类:
import java.sql.Time;
/**
* Created by Josh on 19/8/2015.
*/
public class Timer {
private long endTimeMillis;
public Timer(long hours, long minutes, long seconds){
long currentTime = System.currentTimeMillis();
endTimeMillis = ((seconds*1000 + minutes*1000*60 + hours*1000*60*60) + currentTime);
}
public boolean isFinished(){
long currentTime = System.currentTimeMillis();
if(endTimeMillis - currentTime <= 0){
return true;
}else{
return false;
}
}
public long getRemainingTimeMillis(){
long currentTime = System.currentTimeMillis();
return endTimeMillis - currentTime;
}
}
这是GUI
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
/**
* Created by Josh on 19/8/2015.
*/
public class GUI extends Application {
private BorderPane root;
Label hoursLabel, minutesLabel, secondsLabel;
TextField hoursTF, minutesTF, secondsTF;
Button startButton;
Label remainingTimeLabel;
TextField remainingTimeTF;
long remainingTime;
@Override
public void start(Stage primaryStage) throws Exception {
root = new BorderPane();
primaryStage.setScene(new Scene(root, 1300, 125));
primaryStage.show();
primaryStage.setResizable(true);
primaryStage.setTitle("Timer");
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
event.consume();
System.exit(0);
}
});
FlowPane center = new FlowPane();
hoursLabel = new Label(" Hours: ");
hoursTF = new TextField();
minutesLabel = new Label(" Minutes: ");
minutesTF = new TextField();
secondsLabel = new Label(" Seconds: ");
secondsTF = new TextField();
center.getChildren().addAll(hoursLabel, hoursTF, minutesLabel, minutesTF, secondsLabel, secondsTF);
root.setCenter(center);
FlowPane bottom = new FlowPane();
bottom.setAlignment(Pos.CENTER);
startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
start();
}
});
remainingTimeLabel = new Label(" Time Remaining: ");
remainingTimeTF = new TextField();
bottom.getChildren().addAll(startButton, remainingTimeLabel, remainingTimeTF);
root.setBottom(bottom);
}
public void start(){
Task timer = new Task<Void>() {
@Override
protected Void call() throws Exception {
long hours = 0, minutes = 0, seconds = 0;
try{
if(hoursTF.getText() == null || hoursTF.getText().equals("")){
hours = 0;
}else{
hours = Long.parseLong(hoursTF.getText());
}
if(minutesTF.getText() == null || minutesTF.getText().equals("")){
minutes = 0;
}else{
minutes = Long.parseLong(minutesTF.getText());
}
if(secondsTF.getText() == null || secondsTF.getText().equals("")){
seconds = 0;
}else{
seconds = Long.parseLong(secondsTF.getText());
}
}catch(NumberFormatException e){
System.out.println("Error");
}
Timer timer = new Timer(hours, minutes, seconds);
while(!timer.isFinished()){
remainingTimeTF.setText(Long.toString(timer.getRemainingTimeMillis()));
}
return null;
}
};
new Thread(timer).start();
}
public static void main(String[] args) {
launch(args);
}
}
任何帮助将不胜感激!
新堆栈跟踪:
x.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.scene.control.Labeled.setText(Labeled.java:145)
at GUI$3.call(GUI.java:109)
at GUI$3.call(GUI.java:80)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1423)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:438)
at javafx.scene.Parent$2.onProposedChange(Parent.java:364)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$$Lambda$102/1442917786.call(Unknown Source)
at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.scene.control.Labeled.setText(Labeled.java:145)
at GUI$3.call(GUI.java:109)
at GUI$3.call(GUI.java:80)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1423)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:745)
答案 0 :(得分:2)
首先,如果您确实需要以您的方式更新TextField,我建议您在while时添加Thread.sleep(100);
(每秒10次更新),甚至Thread.sleep(1000);
(每秒1次更新)因为在每个CPU周期更新该文本字段实际上是CPU密集型的,通常你不需要...
其次(可能是你的例外的原因)调用remainingTimeTF.setText();
必须发生在FX线程中,请尝试使用此代码:
Platform.runLater(new Runnable() {
@Override public void run() {
remainingTimeTF.setText(Long.toString(timer.getRemainingTimeMillis()));
}
});
现在你在FX Thead之外设置文本,这可能会导致几个例外,因为更新请求发生在工作线程中,实际的UI修改发生在FX线程中。您需要确保每次需要修改UI时,它都会在正确的线程中发生,在这种情况下是带有Platform.runLater()
调用的FX线程。
此外,由于您已经在使用Task类,为什么不绑定text属性并使用updateMessage()或updateTitle()方法呢? 看一下this article,它有正确使用Task和从工作线程更新UI的很好的例子。
希望这有帮助!
答案 1 :(得分:1)
您违反了关于不在后台线程上访问实时场景图中节点状态的规则。大多数JavaFX控件实际上会检查这个并在执行此操作时抛出IllegalStateException
:显然TextInputControl.setText(...)
没有(大概是出于性能原因)。
最简单的解决方法是更新任务的消息属性:
while(!timer.isFinished()){
updateMessage(Long.toString(timer.getRemainingTimeMillis()));
// remainingTimeTF.setText(Long.toString(timer.getRemainingTimeMillis()));
}
(这导致在FX应用程序线程上更新消息属性),然后将文本字段的text属性绑定到它:
remainingTimeTF.textProperty().bind(timer.messageProperty());
new Thread(timer).start();
但是,您的代码有很多违反良好做法的事情。例如,您应该真正计算任务之外的总时间(即在FX应用程序线程上)并将其存储在final
变量中;繁忙的while循环非常糟糕(尽管updateMessage(...)
将解决与此相关的大部分问题)。
我建议您阅读JavaFX中的多线程。从Task
API docs开始。也许这个问题会有所帮助:Using threads to make database requests