Michael Miner
Michael Miner

Reputation: 964

JavaFX Adding Threads

I have written a program in java using JavaFX and the SceneBuilder application. I would like to add threading to my action handlers. This application runs a python script which discovers and sends a file to to appropriate devices on a BACnet network. The python script works as expected and my java application utilizes the script as expected. My issue is updating the UI. The script returns values showing its progress thus far. When running my upgrade action handler code I want to output a message saying 'This should take xx minutes to complete' before executing the executeCommand() code which will also have an output written to the UI. However all the updates are written at the same time, when the upgrade action handler exits.

Main:

public class Main extends Application {

private Stage primaryStage;
private BorderPane rootLayout;

@Override
public void start(Stage primaryStage) {

    this.primaryStage = primaryStage;
    this.primaryStage.setTitle("NODE-Sensor Configurator");

    initRootLayout();

    showNodeConfigurator();

}

public void initRootLayout( ){
    try {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("view/RootLayout.fxml"));
        rootLayout = (BorderPane) loader.load();

        Scene scene = new Scene(rootLayout);
        primaryStage.setScene(scene);
        primaryStage.show();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void showNodeConfigurator(){
    try {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("view/NodeLayout.fxml"));
        AnchorPane nodeConfig;
        nodeConfig = (AnchorPane) loader.load();
        rootLayout.setCenter(nodeConfig);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public Stage getPrimaryStage(){
    return this.primaryStage;
}

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

My code to send the commands and write the output:

@FXML
private Button upgrade;

upgrade.setOnAction((event) -> {

appendToOutputText("Reading in hex file");

python.executeCommand("readhexfile " + hexFile.getAbsolutePath());

appendToOutputText("Starting upgrade, this will take about 4 minutes to complete");
python.executeCommand("upgrade 158 158444");
)};

private void appendToOutputText(String message){
    outputTextArea.appendText(message + "\n");
}

I have tried placing the appendToOutputText method in a thread, I have tried using the runLater JavaFX code, I even tried using a Task in the action handler. I can not get this output to thread properly. No examples I have found use the scenebuilder and fxloader.

Can anyone shed some light on how I can turn my action handlers into threaded tasks that can update the UI as needed rather than on exit?

This Questions looks like it could be of use. However I not understand how the FXLoader will work with the concept of tasks.

Upvotes: 0

Views: 185

Answers (2)

SedJ601
SedJ601

Reputation: 13859

Given the limited info you shared, I think your code should look like:

upgrade.setOnAction((event) -> {
    appendToOutputText("Reading in hex file");
    Task<Void> task1 = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            python.executeCommand("readhexfile " + hexFile.getAbsolutePath());
            return null;
        }
    };
    task1.setOnSucceeded((event) -> {
        appendToOutputText("Starting upgrade, this will take about 4 minutes to complete");
    });

    Task<Void> task2 = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            python.executeCommand("upgrade 158 158444");
            return null;
        }
    };
    task2.setOnSucceeded((event) -> {
        System.out.println("task2 done!");
    });


    ExecutorService es = Executors.newSingleThreadExecutor();
    es.submit(task1);
    es.submit(task2);
    es.shutdown();
)};

Or something like:

upgrade.setOnAction((event) -> {
    appendToOutputText("Reading in hex file");
    Task<Void> task1 = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            python.executeCommand("readhexfile " + hexFile.getAbsolutePath());
            Platform.runLater(()->{appendToOutputText("Starting upgrade, this will take about 4 minutes to complete");});
            python.executeCommand("upgrade 158 158444");

            return null;
        }
    };
    task1.setOnSucceeded((event) -> {
        System.out.println("task1 done!");
    });

    ExecutorService es = Executors.newSingleThreadExecutor();
    es.submit(task1);
    es.shutdown();
)};

Upvotes: 2

moilejter
moilejter

Reputation: 998

I think your event handler you pass in for setOnAction() will be invoked in the JavaFx application thread, so you will be able to manipulate the UI - but the two calls to executeCommand are probably blocking calls. You would need to create two background threads:

  • the first background thread starts up the external command via executeCommand(), then waits for it to finish.
  • the second background thread starts up and reads the output produced by that external command. This second thread would go into a loop, and every time it reads a new line from the external command, for instance, this second thread would invoke runLater() passing it a block of code that would manipulate the UI to reflect the data just received.

Upvotes: 1

Related Questions