爲什麼你的應用程序無法正常工作
發生的事情是,您在自己的線程上運行任務,在任務中設置seconds屬性,然後該綁定觸發標籤文本的立即更新,同時仍然在任務線程上。
這違反了JavaFX的線程處理rule:
應用程序必須將節點連接場景,並修改那些已經連接到一個場景,在JavaFX應用程序線程節點。
這就是您最初發布的程序無法正常工作的原因。
如何修復
要修改原來的計劃,使其工作,包裹性修改的任務Platform.runLater構造內:
Platform.runLater(new Runnable() {
@Override public void run() {
System.out.println("TIK! " + sec);
seconds.setValue(String.valueOf(sec));
System.out.println("TAK! " + seconds.getValue());
}
});
這確保當您寫出屬性時,您已經在JavaFX應用程序線程中,以便在隨後的更改激發綁定標籤文本時,JavaFX應用程序中也會發生此更改在線程上。
專屬命名約定
該程序不對應的JavaFX豆約定馬太指出,這是事實。遵循這些約定對於使程序更容易理解以及使用像PropertyValueFactory這樣反映屬性方法名稱的東西,以允許表和列表單元格在底層屬性更新時自動更新其值。但是,對於您的示例,不遵循JavaFX bean約定並不能解釋程序無法運行的原因。
替代的解決方案
下面是一個替代的解決方案,以您的倒計時結合問題,它使用了JavaFX animation framework而非concurrency framework。我更喜歡這樣做,因爲它保留了JavaFX應用程序線程上的所有內容,您不必擔心難以理解和調試的併發問題。
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountdownTimer extends Application {
@Override public void start(final Stage stage) throws Exception {
final CountDown countdown = new CountDown(10);
final CountDownLabel countdownLabel = new CountDownLabel(countdown);
final Button countdownButton = new Button(" Start ");
countdownButton.setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent t) {
countdownButton.setText("Restart");
countdown.start();
}
});
VBox layout = new VBox(10);
layout.getChildren().addAll(countdownLabel, countdownButton);
layout.setAlignment(Pos.BASELINE_RIGHT);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
class CountDownLabel extends Label {
public CountDownLabel(final CountDown countdown) {
textProperty().bind(Bindings.format("%3d", countdown.timeLeftProperty()));
}
}
class CountDown {
private final ReadOnlyIntegerWrapper timeLeft;
private final ReadOnlyDoubleWrapper timeLeftDouble;
private final Timeline timeline;
public ReadOnlyIntegerProperty timeLeftProperty() {
return timeLeft.getReadOnlyProperty();
}
public CountDown(final int time) {
timeLeft = new ReadOnlyIntegerWrapper(time);
timeLeftDouble = new ReadOnlyDoubleWrapper(time);
timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new KeyValue(timeLeftDouble, time)
),
new KeyFrame(
Duration.seconds(time),
new KeyValue(timeLeftDouble, 0)
)
);
timeLeftDouble.addListener(new InvalidationListener() {
@Override public void invalidated(Observable o) {
timeLeft.set((int) Math.ceil(timeLeftDouble.get()));
}
});
}
public void start() {
timeline.playFromStart();
}
}
更新任務執行戰略
其他問題是否可以運行多個任務,其中包含一個Platform.runLater(new Runnable())
方法?
是的,您可以使用多個任務。每個任務可以是相同類型或不同類型。
您可以創建單個線程並按順序在線程上運行每個任務,也可以創建多個線程並以並行方式運行任務。
要管理多個任務,您可以創建一個監督Task。有時可以使用Service來管理多個任務,並使用Executors框架來管理多個線程。
有一個例子Task
Service
Executors
協調方式:Creating multiple parallel tasks by a single service In each task。
在每項任務中,您可以不撥打runlater
電話,撥打一個runlater
電話或撥打多個runlater
電話。
所以有很大的靈活性可用。
或者我應該創建一個僅從其他任務獲取數據並更新UI的常規任務?
是的,如果複雜性保證,您可以使用這樣的協調任務方法。在Render 300 charts off screen and save them to files中有這樣一種方法的例子。
非常感謝!你已經啓發了我的想法:D 我有與此相關的其他問題: 是否有可能運行多個包含Platform.runLater(新的Runnable())方法的任務?或者,也許我應該創建一個僅從其他任務獲取數據並更新UI的常規任務? – Wicia
更新的答案與其他問題的信息。可能最好在新問題中提出任何新問題。 – jewelsea