JavaFX ObservableList toTableview через поток задач

Я создаю многоэкранное приложение JavaFX с данными, извлекаемыми из базы данных SQL в ObservableLists и отображаемыми в интерфейсе через Tableview. Из-за многоэкранного характера приложения я пытаюсь инициализировать данные из ObservableList в Tableview через контроллер. Извлечение SQL в ObservableList выполняется через задачу в новом потоке. Когда я включаю sqlCSEditTbl.itemsProperty().setValue((ObservableList) csSQLList) в метод Task, при запуске программы в TableView ничего не отображается. Если я размещу код вне метода Task, конкретный экран не будет отображаться. Я не знаю, чего мне здесь не хватает, чтобы получить данные для отображения на конкретном экране. Я отладил SQL для ObservableList, и данные правильно хранятся в ObservableList. Проблема заключается в том, чтобы получить его из ObservableList в интерфейс Tableview. Любая помощь будет принята с благодарностью. Спасибо!

Снимок экрана редактирования приложения

Модель массива SQLCalcScript

package model.calcs;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * Created by jdsmith on 2/17/2016.
 */
public class SQLCalcScripts {

    private final IntegerProperty calcScriptID;
    private final IntegerProperty calcScriptIndex;
    private final StringProperty calcScriptName;
    private final StringProperty calcScriptServer;
    private final StringProperty calcScriptApp;
    private final StringProperty calcScriptGroup;

    public SQLCalcScripts() {
        this(null, null, null, null, null, null);
    }

    public SQLCalcScripts(Integer calcScriptID, Integer calcScriptIndex, String calcScriptName, String calcScriptServer, String calcScriptApp, String calcScriptGroup) {
        this.calcScriptID = new SimpleIntegerProperty(calcScriptID);
        this.calcScriptIndex = new SimpleIntegerProperty(calcScriptIndex);
        this.calcScriptName = new SimpleStringProperty(calcScriptName);
        this.calcScriptServer = new SimpleStringProperty(calcScriptServer);
        this.calcScriptApp = new SimpleStringProperty(calcScriptApp);
        this.calcScriptGroup = new SimpleStringProperty(calcScriptGroup);

        this.calcScriptID.addListener((e) -> System.out.println("ID changed to " + this.calcScriptID.get()));
        this.calcScriptIndex.addListener((e) -> System.out.println("ID changed to " + this.calcScriptIndex.get()));
        this.calcScriptName.addListener((e) -> System.out.println("ID changed to " + this.calcScriptName.get()));
        this.calcScriptServer.addListener((e) -> System.out.println("ID changed to " + this.calcScriptServer.get()));
        this.calcScriptApp.addListener((e) -> System.out.println("ID changed to " + this.calcScriptApp.get()));
        this.calcScriptGroup.addListener((e) -> System.out.println("ID changed to " + this.calcScriptGroup.get()));
    }

    // Getters and Setters for Calc Script
    public Integer getCalcScriptID() {
        return calcScriptID.get();
    }

    public void setCalcScriptID(Integer calcScriptID) {
        this.calcScriptID.set(calcScriptID);
    }

    public IntegerProperty calcScriptIDProperty() {
        return calcScriptID;
    }


    public Integer getCalcScriptIndex() {
        return calcScriptIndex.get();
    }

    public void setCalcScriptIndex(Integer calcScriptIndex) {
        this.calcScriptIndex.set(calcScriptIndex);
    }

    public IntegerProperty calcScriptIndexProperty() {
        return calcScriptIndex;
    }


    public String getCalcScriptName() {
        return calcScriptName.get();
    }

    public void setCalcScriptName(String calcScriptName) {
        this.calcScriptName.set(calcScriptName);
    }

    public StringProperty calcScriptNameProperty() {
        return calcScriptName;
    }


    public String getCalcScriptServer() {
        return calcScriptServer.get();
    }

    public void setCalcScriptServer(String calcScriptServer) {
        this.calcScriptServer.set(calcScriptServer);
    }

    public StringProperty calcScriptServerProperty() {
        return calcScriptServer;
    }


    public String getCalcScriptApp() {
        return calcScriptApp.get();
    }

    public void setCalcScriptApp(String calcScriptApp) {
        this.calcScriptApp.set(calcScriptApp);
    }

    public StringProperty calcScriptAppProperty() {
        return calcScriptApp;
    }


    public String getCalcScriptGroup() {
        return calcScriptGroup.get();
    }

    public void setCalcScriptGroup(String calcScriptGroup) {
        this.calcScriptGroup.set(calcScriptGroup);
    }

    public StringProperty calcScriptGroupProperty() {
        return calcScriptGroup;
    }
}

csEditInterface.fxml

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

<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="essapp.csEditController">
   <children>
      <VBox alignment="TOP_CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
         <children>
            <Label alignment="CENTER" contentDisplay="CENTER" text="Calculation Script Editor">
               <font>
                  <Font name="System Bold" size="36.0" />
               </font>
            </Label>
            <ScrollPane fitToHeight="true" fitToWidth="true" pannable="true" prefHeight="800.0" prefWidth="717.0">
               <content>
                  <AnchorPane prefHeight="200.0" prefWidth="717.0">
                     <children>
                        <TableView editable="true" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                          <columns>
                            <TableColumn fx:id="csEditID" editable="false" prefWidth="100.0" sortable="false" text="ID" />
                            <TableColumn fx:id="csEditIndex" prefWidth="100.0" text="Index" />
                              <TableColumn fx:id="csEditName" prefWidth="250.0" sortable="false" text="Name" />
                              <TableColumn fx:id="csEditServer" editable="false" prefWidth="300.0" sortable="false" text="Server" />
                              <TableColumn fx:id="csEditApp" editable="false" prefWidth="250.0" sortable="false" text="Application" />
                              <TableColumn fx:id="csEditGroup" prefWidth="400.0" sortable="false" text="Calc Group" />
                          </columns>
                        </TableView>
                     </children>
                  </AnchorPane>
               </content>
               <VBox.margin>
                  <Insets left="50.0" right="50.0" top="50.0" />
               </VBox.margin>
            </ScrollPane>
            <Button fx:id="csEditOkBtn" defaultButton="true" mnemonicParsing="false" onAction="#createTbl" prefHeight="25.0" prefWidth="151.0" text="Commit Changes">
               <VBox.margin>
                  <Insets top="50.0" />
               </VBox.margin>
            </Button>
            <Button fx:id="csEditExitBtn" cancelButton="true" mnemonicParsing="false" onAction="#goToCSInt" prefHeight="25.0" prefWidth="150.0" text="Cancel">
               <VBox.margin>
                  <Insets top="25.0" />
               </VBox.margin>
            </Button>
         </children>
      </VBox>
   </children>
</AnchorPane>

Код csEditController:

package essapp;

import com.sun.javafx.tk.Toolkit;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
import model.calcs.*;

import java.net.URL;
import java.util.Map;
import java.util.ResourceBundle;

import static javafx.scene.input.KeyCode.T;

@SuppressWarnings("Duplicates")
public class csEditController implements Initializable, ControlledScreen {

    ScreensController myController;
    ObservableList<SQLCalcScripts> csEditCSList = FXCollections.observableArrayList();
    ObservableList<CalcScripts> csEditEssSQL = FXCollections.observableArrayList();
    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {

        //Add SQL data to TableView
        Task task = new Task<Void>() {
            @Override
            public Void call() throws Exception {

                SQL2CalcScripts csSQLList = new SQL2CalcScripts();
                csSQLList.sqlCalc("http://TEST-HYPRPT01:13080/aps/JAPI","GNLESB",csEditCSList);
                sqlCSEditTbl.itemsProperty().setValue((ObservableList<SQLCalcScripts>) csSQLList);

                return null;
            }
        };
//        sqlCSEditTbl.itemsProperty().bind(task.valueProperty());
//        sqlCSEditTbl.setItems(csEditCSList);
        new Thread(task).start();

        // Initialize table with columns
        csEditID.setCellValueFactory(cellData -> cellData.getValue().calcScriptIDProperty().asObject());
        csEditIndex.setCellValueFactory(cellData -> cellData.getValue().calcScriptIndexProperty().asObject());
        csEditName.setCellValueFactory(cellData -> cellData.getValue().calcScriptNameProperty());
        csEditServer.setCellValueFactory(cellData -> cellData.getValue().calcScriptServerProperty());
        csEditApp.setCellValueFactory(cellData -> cellData.getValue().calcScriptAppProperty());
        csEditGroup.setCellValueFactory(cellData -> cellData.getValue().calcScriptGroupProperty());

        // TableView Calc Group ComboBox
        ObservableList<String> csGroupList = FXCollections.observableArrayList("Supplement", "Wrapper", "Board Book");
        csEditGroup.setCellFactory(ComboBoxTableCell.forTableColumn(new DefaultStringConverter(), csGroupList));
        csEditGroup.setOnEditCommit(
                (TableColumn.CellEditEvent<SQLCalcScripts,String> cb) -> {
                    ((SQLCalcScripts) cb.getTableView().getItems().get(
                            cb.getTablePosition().getRow()
                    )).setCalcScriptGroup(cb.getNewValue());
                }
        );
    }

    public void setScreenParent(ScreensController screenParent){
        myController = screenParent;
    }

    @FXML // ResourceBundle that was given to the FXMLLoader
    private ResourceBundle resources;

    @FXML // URL location of the FXML file that was given to the FXMLLoader
    private URL location;

    @FXML // fx:id="sqlCSEditTbl"
    private TableView<SQLCalcScripts> sqlCSEditTbl; // Value injected by FXMLLoader

    @FXML // fx:id="csEditGroup"
    private TableColumn<SQLCalcScripts, String> csEditGroup; // Value injected by FXMLLoader

    @FXML // fx:id="csEditExitBtn"
    private Button csEditExitBtn; // Value injected by FXMLLoader

    @FXML // fx:id="csEditServer"
    private TableColumn<SQLCalcScripts, String> csEditServer; // Value injected by FXMLLoader

    @FXML // fx:id="csEditID"
    private TableColumn<SQLCalcScripts, Integer> csEditID; // Value injected by FXMLLoader

    @FXML // fx:id="csEditIndex"
    private TableColumn<SQLCalcScripts, Integer> csEditIndex; // Value injected by FXMLLoader

    @FXML // fx:id="csEditOkBtn"
    private Button csEditOkBtn; // Value injected by FXMLLoader

    @FXML // fx:id="csEditApp"
    private TableColumn<SQLCalcScripts, String> csEditApp; // Value injected by FXMLLoader

    @FXML // fx:id="csEditName"
    private TableColumn<SQLCalcScripts, String> csEditName; // Value injected by FXMLLoader

    @FXML
    private void goToCSInt(ActionEvent event){
        myController.setScreen(ScreensFramework.calcScriptInterfaceID);
    }

//    @FXML
//    private void createTbl(ActionEvent event) {
//        sqlCSEditTbl.setItems(csEditCSList);
//    }
}

Код класса ControlledScreen:

package sample;

/**
 * Created by jdsmith on 4/21/2016.
 */
public interface ControlledScreen {
    //This method will allow the injection of the Parent ScreenPane
    public void setScreenParent(ScreensController screenPage);
}

Код класса ScreensController:

package essapp;

import java.util.HashMap;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

/**
 * Created by jdsmith on 1/7/2016.
 */
public class ScreensController  extends StackPane {
    //Holds the screens to be displayed

    private HashMap<String, Node> screens = new HashMap<>();

    public ScreensController() {
        super();
    }

    //Add the screen to the collection
    public void addScreen(String name, Node screen) {
        screens.put(name, screen);
    }

    //Returns the Node with the appropriate name
    public Node getScreen(String name) {
        return screens.get(name);
    }

    //Loads the fxml file, add the screen to the screens collection and
    //finally injects the screenPane to the controller.
    public boolean loadScreen(String name, String resource) {
        try {
            FXMLLoader myLoader = new FXMLLoader(getClass().getResource(resource));
            Parent loadScreen = (Parent) myLoader.load();
            ControlledScreen myScreenControler = ((ControlledScreen) myLoader.getController());
            myScreenControler.setScreenParent(this);
            addScreen(name, loadScreen);
            return true;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }
    }

    //This method tries to displayed the screen with a predefined name.
    //First it makes sure the screen has been already loaded.  Then if there is more than
    //one screen the new screen is been added second, and then the current screen is removed.
    // If there isn't any screen being displayed, the new screen is just added to the root.
    public boolean setScreen(final String name) {
        if (screens.get(name) != null) {   //screen loaded
            final DoubleProperty opacity = opacityProperty();

            if (!getChildren().isEmpty()) {    //if there is more than one screen
                Timeline fade = new Timeline(
                        new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1.0)),
                        new KeyFrame(new Duration(1000), new EventHandler<ActionEvent>() {
                            @Override
                            public void handle(ActionEvent t) {
                                getChildren().remove(0);                    //remove the displayed screen
                                getChildren().add(0, screens.get(name));     //add the screen
                                Timeline fadeIn = new Timeline(
                                        new KeyFrame(Duration.ZERO, new KeyValue(opacity, 0.0)),
                                        new KeyFrame(new Duration(800), new KeyValue(opacity, 1.0)));
                                fadeIn.play();
                            }
                        }, new KeyValue(opacity, 0.0)));
                fade.play();

            } else {
                setOpacity(0.0);
                getChildren().add(screens.get(name));       //no one else been displayed, then just show
                Timeline fadeIn = new Timeline(
                        new KeyFrame(Duration.ZERO, new KeyValue(opacity, 0.0)),
                        new KeyFrame(new Duration(2500), new KeyValue(opacity, 1.0)));
                fadeIn.play();
            }
            return true;
        } else {
            System.out.println("screen hasn't been loaded!!! \n");
            return false;
        }

        /*Node screenToRemove;
         if(screens.get(name) != null){   //screen loaded
         if(!getChildren().isEmpty()){    //if there is more than one screen
         getChildren().add(0, screens.get(name));     //add the screen
         screenToRemove = getChildren().get(1);
         getChildren().remove(1);                    //remove the displayed screen
         }else{
         getChildren().add(screens.get(name));       //no one else been displayed, then just show
         }
         return true;
         }else {
         System.out.println("screen hasn't been loaded!!! \n");
         return false;
         }*/
    }

    //This method will remove the screen with the given name from the collection of screens
    public boolean unloadScreen(String name) {
        if (screens.remove(name) == null) {
            System.out.println("Screen didn't exist");
            return false;
        } else {
            return true;
        }
    }
}

Код основного приложения ScreensFramework:

package essapp;

import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class ScreensFramework extends Application {

    public static String mainInterfaceID = "main";
    public static String mainInterfaceFile = "mainInterface.fxml";

    public static String msaInterfaceID = "msa";
    public static String msaInterfaceFile = "msaInterface.fxml";

    public static String creditRatingInterfaceID = "credit";
    public static String creditRatingInterfaceFile = "creditRatingInterface.fxml";

    public static String calcScriptEditID = "csEdit";
    public static String calcScriptEditFile = "csEditInterface.fxml";

    public static String calcScriptInterfaceID = "calc";
    public static String calcScriptInterfaceFile = "calcScriptInterface.fxml";

    public static String subVarInterfaceID = "subvar";
    public static String subVarInterfaceFile = "subVarInterface.fxml";

    @Override
    public void start(Stage primaryStage) {

        ScreensController mainContainer = new ScreensController();
        mainContainer.loadScreen(ScreensFramework.mainInterfaceID, ScreensFramework.mainInterfaceFile);
        mainContainer.loadScreen(ScreensFramework.calcScriptInterfaceID, ScreensFramework.calcScriptInterfaceFile);
        mainContainer.loadScreen(ScreensFramework.calcScriptEditID, ScreensFramework.calcScriptEditFile);
        mainContainer.loadScreen(ScreensFramework.subVarInterfaceID, ScreensFramework.subVarInterfaceFile);
        mainContainer.loadScreen(ScreensFramework.msaInterfaceID, ScreensFramework.msaInterfaceFile);
        mainContainer.loadScreen(ScreensFramework.creditRatingInterfaceID, ScreensFramework.creditRatingInterfaceFile);

        mainContainer.setScreen(ScreensFramework.mainInterfaceID);

        mainContainer.prefHeightProperty().bind(primaryStage.heightProperty());
        mainContainer.prefWidthProperty().bind(primaryStage.widthProperty());
        mainContainer.setCenterShape(true);
        mainContainer.setScaleShape(true);

        Group root = new Group();
        root.getChildren().addAll(mainContainer);
        Scene scene = new Scene(root);

        Screen screen = Screen.getPrimary();
        Rectangle2D bounds = screen.getVisualBounds();

        primaryStage.setWidth(bounds.getWidth());
        primaryStage.setHeight(bounds.getHeight());

        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

person Jonathan Smith    schedule 21.04.2016    source источник


Ответы (1)


Когда вы звоните

sqlCSEditTbl.itemsProperty().setValue(...);

вы обновляете пользовательский интерфейс (путем обновления элементов, отображаемых в таблице). Как (почти?) все наборы инструментов пользовательского интерфейса, JavaFX является однопоточным: обновления пользовательского интерфейса могут происходить только в потоке приложения FX. Выполнение этого в Task, который выполняется в фоновом потоке, вызовет IllegalStateException; поэтому ваш метод вызова никогда не завершается, и вы никогда не видите обновление таблицы.

Обычный способ сделать это — вернуть данные из вашего метода call:

    Task<List<SQLCalcScripts>> task = new Task<List<SQLCalcScripts>>() {
        @Override
        public List<SQLCalcScripts> call() throws Exception {

           List<SQLCalcScripts> data = /* get data.... */ ;

           return data;
        }
    };

когда задача завершается, для свойства value устанавливается значение, возвращаемое методом call, поэтому теперь вы можете сделать:

task.setOnSucceeded(e -> sqlCSEditTbl.getItems().setAll(task.getValue()));

Вероятно, хорошей идеей будет регистрировать любые исключения, возникающие с

task.setOnFailed(e -> task.getException().printStackTrace());

Затем, как и раньше, запустите задачу с помощью

new Thread(task).start();
person James_D    schedule 21.04.2016
comment
Спасибо, Джеймс_Д! Это очень помогает. - person Jonathan Smith; 21.04.2016