JavaFX 中的计时器更新标签(绑定)

Timer in JavaFX updating Label (Bindings)

我想在我的应用程序中添加一个时钟,告诉您完成任务的时间。为了简化它,我在新线程中包含了一个每秒递增的计数器,并使用计数器编号更新标签 'setTimer'。为此,我在我的 .fxml 文件中有一个标签 fx:id="setTimer" 并将其导入到我的 class.

 @FXML
    private Label setTimer;

并在我的 class 中创建了另一个 class,它扩展线程 TimerTask 并在每次调用时将计数器递增 1。创建了一个新对象 'text',应始终使用计数器的当前值进行更新。

    SimpleStringProperty text = new SimpleStringProperty("undefined");
public class MyTask extends TimerTask {
        @Override
        public void run() {
            counter++;
            text.set(Integer.toString(counter));
        }
    }

为了让这个 class 每秒调用一次,我在初始化方法中创建了一个计时器并将其设置为一秒。

@Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        MyTask myTask = new MyTask();
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(myTask, 0 , 1000);
        setTimer.textProperty().bind(text);
    }

目前我得到异常 'Not on FX application thread; currentThread = Timer-0'。

我已经尝试了很多方法来解决我的问题,但我没有找到正确的方法。 我想做什么我的想法应该很清楚,如果有人能帮助我,我会很高兴。 我的问题是更新 GUI 中计数器的更改。 它不必像我想象的那样解决,只需要一个关于如何最好地实现它的提示。

谢谢

好吧,我的评论太长了。这就是我尝试做的方式。

  1. 在正在加载的应用程序上启动秒表
  2. 创建一个每隔一段时间自行启动的新线程。
  3. 在那里,以秒为单位从秒表获取时间 (sw.getTime(TimeUntis.seconds))。如果需要 like shown in this SO post
  4. ,请将其转换为小时和分钟
  5. 然后,使用Platform.runLater(new Runnable(){ /* access ui element and write time here */ });
  6. 将时间写入UI

在后台线程中使用 Platform.runLater() 有点混乱,应该避免。 JavaFX 有处理这种事情的机制,您应该使用它。具体来说,Task<> 旨在允许后台线程更新连接到需要在 FXAT 上更新的 JavaFX 屏幕元素的数据。

你可以用 JavaFX 任务做你想做的事,但是在里面使用 Java 定时器似乎是不可能的,因为似乎没有任何办法Java 线程等待 Timer 完成。所以,相反,我使用了一个带有睡眠的“for”循环来做同样的事情。它很笨拙,但它确实演示了如何将任务的部分结果连接到屏幕显示:

public class Sample1 extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(new Timer1(), 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

public class Timer1 extends VBox {

    public Timer1() {
        Text time = new Text();
        Button startButton = new Button("Start");
        Button stopButton = new Button("Stop");
        getChildren().addAll(time, startButton, stopButton);
        startButton.setOnAction(startEvt -> {
            Task<Integer> timerFxTask = new Task<>() {

                {
                    updateValue(0);
                }

                @Override
                protected Integer call() throws Exception {
                    for (int counter = 0; counter <= 1000; counter++) {
                        sleep(1000);
                        updateValue(counter);
                    }
                    return 1000;
                }
            };
            stopButton.setOnAction(stopEvt -> timerFxTask.cancel());
            time.textProperty().bind(Bindings.createStringBinding(() -> timerFxTask.getValue().toString(),
                    timerFxTask.valueProperty()));
            Thread timerThread = new Thread(timerFxTask);
            timerThread.start();
        });
    }
}

但是有一种更好的方法来完成您想要做的事情,它本质上是一个动画 - JavaFX 有一个工具可以做到这一点。通常,人们使用动画来改变 JavaFX 屏幕元素的外观,但您也可以使用它来随着时间的推移为文本的内容设置动画。我在这里所做的是创建一个 IntegerProperty,它可以从起始值过渡到随时间线性插值的结束值,然后将该值绑定到屏幕上文本的 TextProperty。所以你看到它每秒更新一次。

public class Sample1 extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(new Timer2(), 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

public class Timer2 extends VBox {

    public Timer2() {
        Text time = new Text();
        Button startButton = new Button("Start");
        Button stopButton = new Button("Stop");
        getChildren().addAll(time, startButton, stopButton);
        startButton.setOnAction(startEvt -> {
            IntegerProperty counter = new SimpleIntegerProperty(0);
            Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1000), new KeyValue(counter, 1000)));
            stopButton.setOnAction(stopEvt -> timeline.stop());
            time.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(counter.get()), counter));
            timeline.play();
        });
    }
}