I have a seemingly common problem with JavaFX's TableView not displaying my data, but my project structure is complicated enough that I think I am missing something or something is not being properly passed to a different class. I am using the CellValueFactory
method to identify the columns of my TableView<Employee>
object, where Employee
is a class I use to represent employees (ID, name, email, and phone, but only the first two fields are to be displayed in the table). The project uses the H2 embedded SQL database package, but that seems to be working so I didn't include my database package. The relevant parts of this project are structured like this:
java
|__ myorg.scheduler
|__ gui
|__ [Abstract] Controller
|__ Model
|__ ControllerMain
|__ ControllerEmployeeTab
|__ ControllerAddEmployee
|__ MainApp
resources
|__ myorg.scheduler
|__ gui
|__ tabHandler.fxml
|__ employeeTab.fxml
|__ addEmployeePopup.fxml
At this stage, the software should allow the user to add an employee to the database:
Pressing the "Add" button brings up this menu, from addEmployeePopup.fxml
The code is as follows:
Model
Since I need to pass data from one controller to another, I have a Model
class:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import myorg.scheduler.database.EmployeeDAO;
import myorg.scheduler.people.Employee;
import java.sql.SQLException;
public class Model {
private final static Model INSTANCE = new Model();
@FXML
ObservableList<Employee> employeeList = FXCollections.observableArrayList();
public static synchronized Model getInstance() {
return INSTANCE;
}
public void addEmployee(Employee employee) {
employeeList.add(employee);
}
public ObservableList<Employee> getAllEmployees() {
return employeeList;
}
public void setEmployeeList() throws SQLException, ClassNotFoundException {
employeeList = EmployeeDAO.getAllEmployees();
}
}
Controller
All of the classes starting with "Controller" inherit from the abstract Controller
class.
public abstract class Controller {
private Model model;
public void setModel(Model model) {
this.model = model;
}
public Model getModel() {
return model;
}
}
ControllerEmployeeTab
This is where the problem starts: populateTable()
is supposed to get (from the synchronized Model
class) the ObservableList
of employees and populate the TableView
with their IDs and Names. For some reason, it is not doing this.
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import myorg.scheduler.people.Employee;
import java.io.IOException;
import static myorg.scheduler.gui.MainApp.loadEmployeePopup;
public class ControllerEmployeeTab extends Controller {
@FXML
public Button btnAddEmployee;
@FXML
TableView<Employee> employeeTable = new TableView<Employee>();
@FXML
TableColumn<Employee, Integer> employeeIDColumn;
@FXML
TableColumn<Employee, String> employeeNameColumn;
@FXML
public void populateTable() {
employeeIDColumn.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getID()).asObject());
employeeNameColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getName()));
employeeTable.setItems(super.getModel().getAllEmployees());
}
@FXML
private void addEmployeePressed(ActionEvent ae) throws IOException {
loadEmployeePopup();
}
}
ControllerMain
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
public class ControllerMain extends Controller {
@FXML
private ControllerEmployeeTab controllerEmployeeTab;
@FXML
private ControllerAddEmployee controllerAddEmployee;
public ControllerEmployeeTab getControllerEmployeeTab() { return controllerEmployeeTab;}
public void setControllerEmployeeTab(ControllerEmployeeTab controllerEmployeeTab) {
this.controllerEmployeeTab = controllerEmployeeTab;
}
public ControllerAddEmployee getControllerAddEmployee() { return controllerAddEmployee;}
public void setControllerAddEmployee(ControllerAddEmployee controllerAddEmployee) {
this.controllerAddEmployee = controllerAddEmployee;
}
}
ControllerAddEmploye
Employees are added by pressing the add
button which calls addEmployeePressed()
from the ControllerEmployeeTab
class. The ControllerAddEmployee
class handles this, and as far as I can tell employees are added to the database, the ObservableList
of employees, and even the TableView
but for some reason are not displaying. Adding employees to the database is working, but the code for that menu is here:
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.stage.Stage;
import myorg.scheduler.database.EmployeeDAO;
import myorg.scheduler.people.Employee;
import java.sql.SQLException;
public class ControllerAddEmployee extends Controller {
@FXML
private TextField nameField;
@FXML
private TextField emailField;
@FXML
private TextField phoneField;
@FXML
private TextField idField;
@FXML
private void addEmployee(ActionEvent ae) throws SQLException, ClassNotFoundException {
if (idField.getText() == null || nameField.getText() == null || emailField.getText() == null || phoneField.getText() == null ||
idField.getText().equals("") || nameField.getText().equals("") || emailField.getText().equals("") || phoneField.getText().equals("")) {
System.out.println("missing a field");
} else {
Employee newEmployee = new Employee(Integer.parseInt(idField.getText()), nameField.getText(), emailField.getText(), phoneField.getText());
super.getModel().addEmployee(newEmployee);
try {
EmployeeDAO.insertEmployee(newEmployee);
} catch (SQLException e) {
System.out.println("Problem occurred while inserting employee: " + e);
throw e;
}
}
}
}
MainApp
This is where the model is instantiated, and where public void start
is for the JavaFX package. populateTable()
from the ControllerEmployeeTab
class is called in ```start``
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
public class MainApp extends Application {
private static final Model model= Model.getInstance();
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("tabHandler.fxml"));
Parent root = loader.load();
ControllerMain mainController = loader.getController();
mainController.setModel(model);
model.setEmployeeList();
setControllers(mainController);
mainController.getControllerEmployeeTab().populateTable();
Scene mainScene = new Scene(root);
stage.setScene(mainScene);
stage.show();
}
public void setControllers(ControllerMain controller) throws IOException {
FXMLLoader employeeTabLoader = new FXMLLoader();
employeeTabLoader.setLocation(MainApp.class.getResource("employeeTab.fxml"));
employeeTabLoader.load();
controller.setControllerEmployeeTab(employeeTabLoader.getController());
controller.getControllerEmployeeTab().setModel(model);
}
public static void loadEmployeePopup() throws IOException {
FXMLLoader addEmployeeLoader = new FXMLLoader();
addEmployeeLoader.setLocation(MainApp.class.getResource("addEmployeePopup.fxml"));
Parent addEmployeeRoot = addEmployeeLoader.load();
ControllerAddEmployee employeeController = addEmployeeLoader.getController();
employeeController.setModel(model);
Scene newEmployeeScene = new Scene(addEmployeeRoot);
Stage newEmployeeMenu = new Stage();
newEmployeeMenu.initModality(Modality.APPLICATION_MODAL);
newEmployeeMenu.setScene(newEmployeeScene);
newEmployeeMenu.setResizable(false);
newEmployeeMenu.show();
}
public static void main(String[] args) {
launch(args);
}
}
tabHandler.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="myorg.scheduler.gui.ControllerMain"
prefHeight="600.0" prefWidth="800.0">
<TabPane>
<tabs>
<Tab fx:id="employeeTab" text="Employees">
<fx:include fx:id="employeeTabPage" source="employeeTab.fxml"/>
</Tab>
<Tab fx:id="eventTab" text="Events">
<fx:include fx:id="eventTabPage" source="eventTab.fxml"/>
</Tab>
</tabs>
</TabPane>
</AnchorPane>
employeeTab.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="myorg.scheduler.gui.ControllerEmployeeTab"
prefHeight="600.0" prefWidth="800.0">
<children>
<HBox AnchorPane.bottomAnchor="531.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<ToolBar prefHeight="40.0" prefWidth="800.0">
<items>
<Button fx:id="btnAddEmployee" mnemonicParsing="false" onAction="#addEmployeePressed" text="Add" />
</items>
</ToolBar>
</children>
</HBox>
<SplitPane dividerPositions="0.3" layoutY="39.0" prefHeight="532.0" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="39.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<TableView fx:id="employeeTable" layoutX="18.0" layoutY="34.0" prefHeight="530.0" prefWidth="235.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="employeeIDColumn" prefWidth="73.0" text="ID" />
<TableColumn fx:id="employeeNameColumn" prefWidth="161.0" text="Name" />
</columns>
</TableView>
</children></AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="557.0" />
</items>
</SplitPane>
</children></AnchorPane>
addEmployeePopup.fxml
I don't think the issue is here but regardless:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<AnchorPane prefHeight="500.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myorg.scheduler.gui.ControllerAddEmployee">
<children>
<TitledPane animated="false" prefHeight="500.0" prefWidth="500.0" text="Add Employee" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<GridPane layoutX="87.6" layoutY="41.6" prefHeight="191.0" prefWidth="307.0" AnchorPane.bottomAnchor="251.0" AnchorPane.leftAnchor="78.0" AnchorPane.rightAnchor="113.0" AnchorPane.topAnchor="32.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="219.0" minWidth="10.0" prefWidth="156.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="304.0" minWidth="10.0" prefWidth="227.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="40.0" minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="39.0" minHeight="10.0" prefHeight="38.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="ID:" GridPane.halignment="RIGHT">
<font>
<Font size="18.0" />
</font>
<GridPane.margin>
<Insets right="10.0" />
</GridPane.margin>
</Label>
<Label text="Full Name:" textAlignment="RIGHT" GridPane.halignment="RIGHT" GridPane.rowIndex="1">
<font>
<Font size="18.0" />
</font>
<GridPane.margin>
<Insets right="10.0" />
</GridPane.margin>
</Label>
<Label text="Email:" GridPane.halignment="RIGHT" GridPane.rowIndex="2">
<font>
<Font size="18.0" />
</font>
<GridPane.margin>
<Insets right="10.0" />
</GridPane.margin>
</Label>
<Label text="Phone:" GridPane.halignment="RIGHT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets right="10.0" />
</GridPane.margin>
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="idField" GridPane.columnIndex="1">
<GridPane.margin>
<Insets left="10.0" right="10.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="nameField" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="10.0" right="10.0" />
</GridPane.margin></TextField>
<TextField fx:id="emailField" GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="10.0" right="10.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="phoneField" GridPane.columnIndex="1" GridPane.rowIndex="3">
<opaqueInsets>
<Insets />
</opaqueInsets>
<GridPane.margin>
<Insets left="10.0" right="10.0" />
</GridPane.margin>
</TextField>
</children>
</GridPane>
<ButtonBar layoutX="171.0" layoutY="435.0">
<buttons>
<Button fx:id="btnCloseAddEmployee" mnemonicParsing="false" onAction="#closeAddEmployeeMenu" text="Cancel" />
<Button mnemonicParsing="false" onAction="#addEmployee" prefHeight="25.0" prefWidth="137.0" text="Add Employee" />
</buttons>
</ButtonBar>
</children></AnchorPane>
</content>
</TitledPane>
</children>
</AnchorPane>
In summary, employees are added to the TableView
but not displayed, and I cannot figure out why. Thank you in advance for any assistance.
@James_D's comment helped me find this solution:
new tabHandler.fxml
for clarity of variables
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="myorg.scheduler.gui.ControllerMain"
prefHeight="600.0" prefWidth="800.0">
<TabPane>
<tabs>
<Tab text="Employees">
<fx:include fx:id="employeeTabPage" source="employeeTab.fxml"/>
</Tab>
<Tab text="Events">
<fx:include fx:id="eventTabPage" source="eventTab.fxml"/>
</Tab>
</tabs>
</TabPane>
</AnchorPane>
I edited ControllerMain
to instead include the FXML AnchorPane
and corresponding ControllerEmployeeTab
variables :
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
public class ControllerMain extends Controller {
@FXML
public AnchorPane employeeTabPage;
@FXML
private ControllerEmployeeTab employeeTabPageController;
public ControllerEmployeeTab getControllerEmployeeTab() { return employeeTabPageController;}
}
And finally removed setControllers()
entirely and changed that call (in start()
) to mainController.getControllerEmployeeTab().setModel(model)