JavaFX:如何使用Task将值从后台线程传递到JavaFX UI线程

时间:2016-05-20 09:27:22

标签: java multithreading javafx-8

我有一个工作任务,它不断使用Web服务并返回整数值。

我的应用程序由不同的(30)文本框和相关的单选按钮组成。 当我单击单选按钮时,我想将后台任务的值显示在单选按钮旁边的文本字段中。

在这种特殊情况下,如何将值从后台线程传递给JAVAFX UI线程?

我是JavaFX的新手并使用以下方式。这是正确的做法吗?

@FXML
private TabPane mainTabPane;

在主窗口控制器中创建任务,并在主窗口控制器中运行

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

创建任务:

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));

        }

}; }

1 个答案:

答案 0 :(得分:5)

一个解决方案(下面详细介绍)是让UI的控制器公开一个属性,并在控制器内部监听此属性的更改以更新显示。然后编排应用程序的组件将控制器的属性绑定到服务的lastValueProperty

一个简单,经过测试的实现:

鉴于以下服务:

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

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

......和任务:

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

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

我们创建UI的控制器,valueProperty最终将绑定到服务,只要服务值或选定的切换发生变化,就会调用方法updateUi()

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; }
}

注意:为了简单起见,updateUi()的实现非常幼稚,并且基于以下FXML;你可能想要在现实应用中更聪明的东西:

<?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>

最后是创建组件并绑定其属性的应用程序主类:

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);
    }
}