0

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

 @FXML
    private Label setTimer;

并在我的类中创建了另一个类,它扩展了线程 TimerTask 并在每次调用时将计数器加一。创建了一个新的对象“文本”,它应该始终使用计数器的当前值进行更新。

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

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

@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);
    }

目前我收到异常“不在 FX 应用程序线程上;currentThread = Timer-0'。

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

谢谢

4

2 回答 2

0

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

  1. 在正在加载的应用程序上启动秒表
  2. 创建一个经常启动的新线程。
  3. 在那里,从秒表获取时间(以秒为单位sw.getTime(TimeUntis.seconds))。如果你想像this SO post中显示的那样将其转换为小时和分钟
  4. 然后,将时间写入 UI 使用Platform.runLater(new Runnable(){ /* access ui element and write time here */ });
于 2020-12-21T11:11:57.140 回答
0

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

您可以使用 JavaFX 任务执行您尝试执行的操作,但在其中使用 Java Timer 似乎是不可能的,因为 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();
        });
    }
}

But there is a better way to do what you're trying to do, which is essentially an animation - and JavaFX has a facility to do exactly this. Usually, people use animations to morph the appearance of JavaFX screen elements, but you can also use it to animate the contents of a Text over time as well. What I've done here is create an IntegerProperty which can be transitioned from a start value to an end value interpolated linearly over time and then bound that value to the TextProperty of a Text on the screen. So you see it update once per second.

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();
        });
    }
}
于 2020-12-21T15:32:05.980 回答