5

私はここの新しい男です:)

JavaFX でのバインディングに関する小さな問題があります。時計として機能し、特別なラベル ( label_Time )に設定する必要がある値を返す Task を作成しました。このラベルは、クイズでプレーヤーの回答まであと何秒かを示します。

問題は、タイマー タスクを使用してラベルの値を自動的に変更する方法です。タイマー Task ( seconds ) の値を label_Time の値にこのような方法でリンクしようとしました...

label_Time.textProperty().bind(timer.getSeconds());

...しかし、うまくいきません。これを行う方法はありますか?

ご回答ありがとうございます。:)


Controller クラスのメソッドを初期化します。

public void initialize(URL url, ResourceBundle rb) {

        Timer2 timer = new Timer2();
        label_Time.textProperty().bind(timer.getSeconds());
        new Thread(timer).start();  
}

タスククラス「Timer2」:

public class Timer2 extends Task{

    private static final int SLEEP_TIME = 1000;
    private static int sec;
    private StringProperty seconds;


    public Timer2(){
        Timer2.sec = 180;
        this.seconds = new SimpleStringProperty("180");
    }

    @Override protected StringProperty call() throws Exception {


        int iterations;

        for (iterations = 0; iterations < 1000; iterations++) {
            if (isCancelled()) {
                updateMessage("Cancelled");
                break;
            }

            System.out.println("TIK! " + sec);
            seconds.setValue(String.valueOf(sec));
            System.out.println("TAK! " + seconds.getValue());

            // From the counter we subtract one second
            sec--;

            //Block the thread for a short time, but be sure
            //to check the InterruptedException for cancellation
            try {
                Thread.sleep(10);
            } catch (InterruptedException interrupted) {
                if (isCancelled()) {
                    updateMessage("Cancelled");
                    break;
                }
            }
        }
        return seconds;
    }

    public StringProperty getSeconds(){
        return this.seconds;
    }

}
4

2 に答える 2

11

アプリが機能しない理由

何が起こっているかというと、タスクを独自のスレッドで実行し、タスクに秒プロパティを設定すると、バインディングはタスク スレッド上にある間にラベル テキストの即時更新をトリガーします。

これは、 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にそのようなアプローチの例があり、それらをファイルに保存します

于 2013-04-02T00:34:21.730 に答える