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

前端 未结 1 389

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 butto

相关标签:
1条回答
  • 2021-02-10 14:18

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题