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