アプリが機能しない理由
何が起こっているかというと、タスクを独自のスレッドで実行し、タスクに秒プロパティを設定すると、バインディングはタスク スレッド上にある間にラベル テキストの即時更新をトリガーします。
これは、 JavaFX スレッド処理の規則に違反しています。
アプリケーションは、ノードをシーンにアタッチし、すでにシーンにアタッチされているノードを 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 アプリケーション スレッド上で発生します。
プロパティの命名規則について
Matthew が指摘するように、プログラムが JavaFX Bean 規則に対応していないことは事実です。これらの規則に準拠することは、プログラムをより理解しやすくするのに役立ちます。また、PropertyValueFactory などのプロパティメソッド名を反映して、基になるプロパティが更新されたときにテーブルとリストのセルの値を自動的に更新できるようにするのにも役立ちます。ただし、あなたの例では、JavaFX Bean の規則に従わないと、プログラムが機能しない理由が説明されません。
代替ソリューション
同時実行フレームワークではなくJavaFXアニメーション フレームワークを使用する、カウントダウン バインディングの問題に対する代替ソリューションを次に示します。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())
メソッドを含む複数のタスクを実行することは可能ですか?
はい、複数のタスクを使用できます。各タスクは、同じタイプまたは異なるタイプにすることができます。
単一のスレッドを作成してそのスレッドで各タスクを順番に実行することも、複数のスレッドを作成してタスクを並行して実行することもできます。
複数のタスクを管理するために、監督タスクを作成できます。複数のタスクを管理するためにServiceを使用し、複数のスレッドを管理するため にExecutorsフレームワークを使用することが適切な場合があります。
Task
, Service
,Executors
調整アプローチの例があります:各タスクで単一のサービスによって複数の並列タスクを作成します。
各タスクでは、電話をかけないこともrunlater
、単一のrunlater
電話をかけることも、複数のrunlater
電話をかけることもできます。
そのため、非常に多くの柔軟性が利用可能です。
それとも、他のタスクからデータを取得して UI を更新するだけの一般的なタスクを 1 つ作成する必要がありますか?
はい、複雑さが必要な場合は、このような調整タスク アプローチを使用できます。Render 300 charts off screenにそのようなアプローチの例があり、それらをファイルに保存します。