如何使预定事件与JavaFX GUI进行交互

时间:2017-06-10 14:22:33

标签: java user-interface javafx concurrency scheduled-tasks

我是一名初学Java程序员,试图解决这个问题。我有一段代码可以进行一些计算并在我的JavaFX GUI中更新标签。它使用100msScheduledExecutorService每隔Runnable运行一次。问题是它无法更新GUI的Label。我昨天一直在寻找一种方法来实现它,大多数主题似乎都是通过使用Platform.runLater来解决的,但即使将我的代码放入runLater runnable似乎仍然无效。我发现的另一件事是使用Java并发框架,但我不知道如何将它用于像这样的重复调度服务。这是我编写代码的方式:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    Runnable loop = new Runnable() {
        public void run() {
             Platform.runLater(new Runnable() {
                 @Override public void run() {
                     double result = calculation();
                     labelResult.setText("" + result);
                 }
             });
        }
    };
    executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);

我怎么能这样做?

编辑: 我包括一个完整的例子。 主要课程:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.application.Platform;

public class Main{
    private static long value = 0;
    private static Gui gui;

    public static void main(String[] args){

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
         Runnable loop = new Runnable() {
                public void run() {
                     Platform.runLater(new Runnable() {
                         @Override public void run() {
                             calculate();
                         }
                     });
                }
            };
        executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);

        Application.launch(Gui.class, args);
    }

    public static void calculate(){
        double result = value++;
        gui.setResult(result);
    }

    public static void setGui(Gui ref){
        gui = ref;
    }
}

桂班:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Gui extends Application{
    private Stage window;
    private Scene scene;
    private HBox layout = new HBox();
    private Label result = new Label("TEST");

    @Override
    public void start(Stage stage) throws Exception {
        window = stage;

        layout.getChildren().addAll(result);

        Main.setGui(this);

        scene = new Scene(layout, 1280, 720);
        window.setTitle("Example");
        window.setResizable(false);
        window.setScene(scene);
        window.show();
    }

    public void setResult(double res){
        result.setText("" + res);
    }
}

1 个答案:

答案 0 :(得分:1)

您的应用程序的整体结构是错误的。您的计划执行程序服务失败的原因是您在启动JavaFX应用程序之前尝试启动它,因此您在FX工具包启动之前和FX应用程序线程运行之前首次调用Platform.runLater(...)

如果您将来自Platform.runLater()块中的try以及catch的例外情况包裹起来,那么例外:

Runnable loop = new Runnable() {
    public void run() {
        try {
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    calculate();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};

您将看到异常:

java.lang.IllegalStateException: Toolkit not initialized
    at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
    at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
    at javafx.application.Platform.runLater(Platform.java:83)
    at Main$1.run(Main.java:17)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

(顺便说一下,处理异常也会让执行者继续,所以最终它会“恢复”,因为工具包会在某个时刻启动。你也可能会看到其他异常,因为,例如,有竞争条件gui字段:在gui初始化之前,可能会调用执行程序的某些迭代。)

您应该将Application.start()方法视为应用程序的入口点。当您调用launch()时(或在大多数最终部署方案中为您调用它时),FX Toolkit将启动,然后创建Application子类的实例,并{{1在FX应用程序线程上的该实例上调用。

所以构建它的方法是从start()方法驱动它。在那里创建GUI类的实例,创建运行预定执行程序的类的实例,将它们绑定在一起,然后在提供的阶段中显示UI。以下是此重构的一个可能示例:

Main.java:

start()

UpdateService.java:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application{
    private Stage window;

    @Override
    public void start(Stage stage) throws Exception {
        window = stage;

        Gui gui = new Gui();
        UpdateService service = new UpdateService(gui);
        service.startService();

        window.setTitle("Example");
        window.setResizable(false);
        window.setScene(gui.getScene());
        window.show();
    }

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


}

Gui.java:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javafx.application.Platform;

public class UpdateService {
    private long value = 0;
    private final Gui gui;

    public UpdateService(Gui gui) {
        this.gui = gui;
    }

    public void startService() {

        // create executor that uses daemon threads;
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t;
        });
        Runnable loop = new Runnable() {
            public void run() {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        calculate();
                    }
                });
            }
        };

        executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);

    }

    public void calculate() {
        double result = value++;
        gui.setResult(result);
    }

}

最后,请注意,在FX应用程序线程上运行定期重复功能的更简洁方法是使用动画API(如JavaFX periodic background task中所示):

import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;

public class Gui {

    private Scene scene;
    private HBox layout = new HBox();
    private Label result = new Label("TEST");

    public Gui() {
        layout.getChildren().addAll(result);
        scene = new Scene(layout, 1280, 720);
    }

    public Scene getScene() {
        return scene ;
    }

    public void setResult(double res){
        result.setText("" + res);
    }
}