5

我是这里的新人:)

我有一个关于 JavaFX 绑定的小问题。我创建了作为时钟工作的 Task 并返回必须在特殊标签(label_Time)中设置的值。此标签显示玩家在测验中的答案还剩多少秒。

问题是如何使用计时器任务自动更改标签中的值?我试图以这种方式将计时器任务()中的值链接到 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

为什么您的应用无法运行

发生的情况是您在它自己的线程上运行任务,在任务中设置 seconds 属性,然后绑定触发标签文本的立即更新,同时仍在任务线程上。

这违反了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());
    }
  });

这可确保当您写出属性时,您已经在 J​​avaFX 应用程序线程上,因此当绑定标签文本的后续更改触发时,该更改也将发生在 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 的一般任务?

是的,如果复杂性需要,您可以使用这样的协调任务方法。在Render 300 charts off screen and save them to files中有一个这种方法的例子。

于 2013-04-02T00:34:21.730 回答
-1

您的“Timer2”类不符合 JavaFX bean 约定:

public String getSeconds();
public void setSeconds(String seconds);
public StringProperty secondsProperty();
于 2013-04-13T18:07:04.287 回答