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));
}
}; }
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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With