Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX : How to pass value from background thread to JavaFX UI thread with Task

I have one worker task which continuously consumes web service and returns integer value.

My Application consist of different (30) text boxes with associated radio button. When I click radio button, I want to display value from background task into Text field next to radio button.

How I can Pass value from background thread to JAVAFX UI thread in this particular situation?

I am new to JavaFX and used following way. Is this correct approach?

@FXML
private TabPane mainTabPane;

Created Task in main window controller and also running in main window controller

myScheduledService.setPeriod(Duration.seconds(10));
myScheduledService.start();

And created task :

private class MyScheduledService extends ScheduledService {

    @Override
    protected Task createTask() {
        return  new Task() {
            @Override
            protected String call() throws IOException, ParseException {
        // calling web service here
        Integer data = callToMyWebService();
        Tab tab = mainTabPane.getTabs().get(mainTabPane.getTabs().size() - 1);
        BorderPane pane = (BorderPane) tab.getContent();
                    HBox hBox = ((HBox) pane.getChildren().get(0));
        Text operationValueDisplayText = (Text) hbox.getChildren().get(2);
        // displaying data in text box
        operationValueDisplayText.setText(String.valueOf(data));

        }

}; }

like image 495
MR.K Avatar asked May 20 '16 09:05

MR.K


1 Answers

One solution (implemented in detail below) is to have the controller of the UI expose a property and, inside the controller, listen to changes of this property to update the display. Then the component orchestrating the application binds the property of the controller to the lastValueProperty of the service.


A simple, tested implementation:

Given the following service:

import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;

public class BgrdService extends ScheduledService<String> {
    @Override protected Task<String> createTask() {
        return new BgrdTask();
    }
}

... and task:

import java.util.Date;
import javafx.concurrent.Task;

class BgrdTask extends Task<String> {
    @Override protected String call() throws Exception {
        return new Date().toString();
    }
}

We create the controller of the UI, where the valueProperty will ultimately be bound to the service and the method updateUi() is called whenever the service value or the selected toggle changes:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.GridPane;

public class MainController {

    @FXML
    private ToggleGroup myToggleGroup;

    @FXML
    private GridPane grid;

    @FXML
    private Label label;

    private StringProperty valueProperty = new SimpleStringProperty("");

    @FXML
    void initialize() {
        valueProperty.addListener((observable, oldValue, newValue) -> {
            updateUi();
        });
        myToggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
            updateUi();
        });
    }

    private void updateUi() {
        String displayValue = getValue();
        label.setText(displayValue);
        int index = myToggleGroup.getToggles().indexOf(myToggleGroup.getSelectedToggle());
        if( index >= 0 ) {
            TextField textField = (TextField) grid.getChildren().get(index);
            textField.setText(displayValue);
        }
    }

    public String getValue() { return valueProperty.get(); }
    public void setValue(String value) { valueProperty.set(value); }
    public StringProperty valueProperty() { return this.valueProperty; }
}

NOTE: To keep things simple, the implementation of updateUi() is very naive and based on the following FXML; you probably want something smarter in a real-life app:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
    <fx:define>
        <ToggleGroup fx:id="myToggleGroup"/>
    </fx:define>
    <children>
        <GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="600.0" fx:id="grid">
            <columnConstraints>
                <ColumnConstraints hgrow="NEVER" minWidth="10.0" prefWidth="300.0"/>
                <ColumnConstraints halignment="CENTER" hgrow="NEVER" minWidth="40.0"/>
            </columnConstraints>
            <rowConstraints>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER"/>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER"/>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER"/>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER"/>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER"/>
            </rowConstraints>
            <children>
                <TextField/>
                <TextField GridPane.rowIndex="1"/>
                <TextField GridPane.rowIndex="2"/>
                <TextField GridPane.rowIndex="3"/>
                <TextField GridPane.rowIndex="4"/>
                <RadioButton toggleGroup="$myToggleGroup" mnemonicParsing="false" GridPane.columnIndex="1" />
                <RadioButton toggleGroup="$myToggleGroup" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                <RadioButton toggleGroup="$myToggleGroup" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.rowIndex="2" />
                <RadioButton toggleGroup="$myToggleGroup" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.rowIndex="3" />
                <RadioButton toggleGroup="$myToggleGroup" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.rowIndex="4" />
            </children>
        </GridPane>
        <Label fx:id="label" text="Label">
            <VBox.margin>
                <Insets top="30.0"/>
            </VBox.margin>
        </Label>
    </children>
</VBox>

Finally the application main class that creates the components and binds their properties:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        BgrdService bgrdService = new BgrdService();
        bgrdService.setPeriod(Duration.millis(3000.0));
        bgrdService.start();

        FXMLLoader loader = new FXMLLoader(this.getClass().getResource("Main.fxml"));
        MainController mainController = new MainController();
        loader.setController(mainController);
        Parent root = loader.load();

        mainController.valueProperty().bind(bgrdService.lastValueProperty());

        Scene scene = new Scene(root, 500, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String... args) {
        launch(args);
    }
}
like image 90
Nikos Paraskevopoulos Avatar answered Nov 10 '22 23:11

Nikos Paraskevopoulos