I've created an FXML file that allows users to see if a patient has a surgery listed in a TableView
and also has the ability to add in a new surgery. To add in a new surgery the user right clicks on the TableView
and a context menu appears with the menu item 'Add Surgery'. A new window appears with text fields to add in the new surgery. These are two separate FXML files with each their own controller files.
Ideal outcome - When a user adds in a new surgery, I want the TableView
to refresh and add in the new data.
Within the PatientModule Controller I have the following:
@FXML
public TableView<ISurgeryModel> surgeryTableView;
ObservableList<ISurgeryModel> initialPTSurgData(){
ISurgeryModel ptSur1 = new ISurgeryModel("10/20/2023", "Other", "L", "surgeon", "facility", "comments");
return FXCollections.observableArrayList(ptSur1);
}
@Override
public void initialize(URL PatientModule, ResourceBundle resourceBundle) {
surgeryDate.setCellValueFactory((new PropertyValueFactory<ISurgeryModel, String>("surgeryDate")));
surgeryProcedure.setCellValueFactory((new PropertyValueFactory<ISurgeryModel, String>("surgeryProcedure")));
surgerySide.setCellValueFactory((new PropertyValueFactory<ISurgeryModel, String>("surgerySide")));
surgeryTableView.setItems(initialPTSurgData());
ContextMenu surgContext = new ContextMenu();
MenuItem addSurgery = new MenuItem("Add Surgery");
MenuItem viewSurgDetails = new MenuItem("View Surgery Details");
MenuItem deleteSurg = new MenuItem("Delete Surgery");
surgContext.getItems().add(addSurgery);
surgContext.getItems().add(viewSurgDetails);
surgContext.getItems().add(deleteSurg);
surgeryTableView.setContextMenu(surgContext);
addSurgery.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
try {
Parent popUp;
popUp = FXMLLoader.load(Objects.requireNonNull(GaitApplication.class.getResource("Wizards/AddSurgery.fxml")));
Stage stage1 = new Stage();
stage1.setTitle("Add Surgery: ");
stage1.setScene(new Scene(popUp, 600, 480));
stage1.show();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
The AddSurgeryController
has the following:
@FXML
private PatientModuleController patientModuleController;
@FXML
public void onSaveSurgery(ActionEvent event){
ISurgeryModel newData = new ISurgeryModel(date.getText(), procedure.getText(), side.getText(), surgeon.getText(), facility.getText(), comments.getText());
patientModuleController.surgeryTableView.getItems().add(newData);
this.addSurgery.getScene().getWindow().hide();
}
Currently when hitting the save button I am given the following error:
Cannot read field "surgeryTableView" because "this.patientModuleController" is null
Do I need to create a constructor for patientModuleController
in the AddSurgeryController
?
Here is an example performing create, update, and delete operations on a table of data utilizing separate dialog windows and FXML.
It uses some of the concepts that were discussed in the comments.
A model is created that contains an observable list with extractors.
The passing parameters option is used to pass data between the calling window and the dialog. It could have used the model for this (e.g. adding an observable currentFriend object property to the model to represent the friend currently being edited and passing the entire model to the new dialog controllers), but that wasn't necessary here.
Add functionality
This code demonstrates adding a new row to a table using data created in a new dialog window.
void addFriend() {
int selectedIndex = friendsTable.getSelectionModel().getSelectedIndex();
try {
Friend newFriend = showFriendDialog(
new Friend(null, null),
"Add Friend"
);
if (newFriend != null) {
friendsTable.getItems().add(
selectedIndex + 1,
newFriend
);
status.setText(
"Added " + newFriend.getFirstName() + " " + newFriend.getLastName()
);
}
} catch (IOException e) {
status.setText("Unable to add a friend.");
e.printStackTrace();
}
}
private Friend showFriendDialog(Friend friend, String title) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
FriendApplication.class.getResource(
"friend.fxml"
)
);
Parent friendPane = fxmlLoader.load();
FriendController addFriendController = fxmlLoader.getController();
addFriendController.setFriend(friend);
Stage addFriendStage = new Stage(StageStyle.UTILITY);
addFriendStage.initModality(Modality.WINDOW_MODAL);
addFriendStage.initOwner(getMyWindow());
addFriendStage.setTitle(title);
addFriendStage.setX(getMyWindow().getX() + getMyWindow().getWidth());
addFriendStage.setY(getMyWindow().getY());
addFriendStage.setScene(new Scene(friendPane));
addFriendStage.showAndWait();
return addFriendController.isSaved()
? friend
: null;
}
Example Code
module-info.java
module com.example.friends {
requires javafx.controls;
requires javafx.fxml;
opens com.example.friends.controllers to javafx.fxml;
exports com.example.friends;
}
FriendApplication.java
package com.example.friends;
import com.example.friends.controllers.FriendsController;
import com.example.friends.model.Model;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class FriendApplication extends Application {
private Model model;
@Override
public void start(Stage stage) throws IOException {
model = new Model();
FXMLLoader fxmlLoader = new FXMLLoader(
FriendApplication.class.getResource(
"friends.fxml"
)
);
Parent root = fxmlLoader.load();
FriendsController controller = fxmlLoader.getController();
controller.initModel(model);
stage.setTitle("Friends");
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Friend.java
package com.example.friends.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Friend {
private final StringProperty firstName;
private final StringProperty lastName;
public Friend(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public String getFirstName() {
return firstName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public String getLastName() {
return lastName.get();
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
@Override
public String toString() {
return "Friend{" +
"firstName=" + getFirstName() +
", lastName=" + getLastName() +
'}';
}
}
Model.java
package com.example.friends.model;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Model {
private final ObservableList<Friend> friends = FXCollections.observableArrayList(friend ->
new Observable[] {
friend.firstNameProperty(),
friend.lastNameProperty()
}
);
public Model() {
initTestData();
}
private void initTestData() {
friends.setAll(
new Friend("Sue", "Shallot"),
new Friend("Perry", "Parsnip")
);
}
public ObservableList<Friend> getFriends() {
return friends;
}
}
FriendsController.java
package com.example.friends.controllers;
import com.example.friends.FriendApplication;
import com.example.friends.model.Friend;
import com.example.friends.model.Model;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import java.io.IOException;
public class FriendsController {
@FXML
private TableView<Friend> friendsTable;
@FXML
private TableColumn<Friend, String> firstNameColumn;
@FXML
private TableColumn<Friend, String> lastNameColumn;
@FXML
private Button add;
@FXML
private Button edit;
@FXML
private Button remove;
@FXML
private Label status;
private Model model;
@FXML
void addFriend() {
int selectedIndex = friendsTable.getSelectionModel().getSelectedIndex();
try {
Friend newFriend = showFriendDialog(
new Friend(null, null),
"Add Friend"
);
if (newFriend != null) {
friendsTable.getItems().add(
selectedIndex + 1,
newFriend
);
status.setText(
"Added " + newFriend.getFirstName() + " " + newFriend.getLastName()
);
}
} catch (IOException e) {
status.setText("Unable to add a friend.");
e.printStackTrace();
}
}
@FXML
void editFriend() {
Friend selectedFriend = friendsTable.getSelectionModel().getSelectedItem();
if (selectedFriend == null) {
return;
}
try {
Friend editedFriend = showFriendDialog(
selectedFriend,
"Edit Friend"
);
if (editedFriend != null) {
status.setText(
"Edited " + editedFriend.getFirstName() + " " + editedFriend.getLastName()
);
}
} catch (IOException e) {
status.setText("Unable to add a friend.");
e.printStackTrace();
}
}
@FXML
void removeFriend() {
ObservableList<Friend> friendsToRemove =
friendsTable.getSelectionModel().getSelectedItems();
if (friendsToRemove.isEmpty()) {
return;
}
friendsTable.getItems().removeAll(
friendsTable.getSelectionModel().getSelectedItems()
);
status.setText(
"Removed " + friendsToRemove.size() + " friend(s)."
);
}
@FXML
private void initialize() {
edit.disableProperty().bind(
friendsTable.getSelectionModel().selectedItemProperty().isNull()
);
remove.disableProperty().bind(
friendsTable.getSelectionModel().selectedItemProperty().isNull()
);
friendsTable.getSelectionModel().setSelectionMode(
SelectionMode.MULTIPLE
);
firstNameColumn.setCellValueFactory(param ->
param.getValue().firstNameProperty()
);
lastNameColumn.setCellValueFactory(param ->
param.getValue().lastNameProperty()
);
}
private Friend showFriendDialog(Friend friend, String title) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
FriendApplication.class.getResource(
"friend.fxml"
)
);
Parent friendPane = fxmlLoader.load();
FriendController addFriendController = fxmlLoader.getController();
addFriendController.setFriend(friend);
Stage addFriendStage = new Stage(StageStyle.UTILITY);
addFriendStage.initModality(Modality.WINDOW_MODAL);
addFriendStage.initOwner(getMyWindow());
addFriendStage.setTitle(title);
addFriendStage.setX(getMyWindow().getX() + getMyWindow().getWidth());
addFriendStage.setY(getMyWindow().getY());
addFriendStage.setScene(new Scene(friendPane));
addFriendStage.showAndWait();
return addFriendController.isSaved()
? friend
: null;
}
private Window getMyWindow() {
return friendsTable.getScene().getWindow();
}
public void initModel(Model model) {
friendsTable.setItems(model.getFriends());
this.model = model;
}
}
FriendController.java
package com.example.friends.controllers;
import com.example.friends.model.Friend;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class FriendController {
@FXML
private TextField firstName;
@FXML
private TextField lastName;
@FXML
private Button save;
@FXML
private Button cancel;
private Friend friend;
private boolean saved;
@FXML
private void initialize() {
ButtonBar.setButtonData(
save,
ButtonBar.ButtonData.APPLY
);
ButtonBar.setButtonData(
cancel,
ButtonBar.ButtonData.CANCEL_CLOSE
);
save.setDefaultButton(true);
cancel.setCancelButton(true);
save.disableProperty().bind(
firstName.textProperty().isEmpty()
.or(
lastName.textProperty().isEmpty()
)
);
}
public void setFriend(Friend friend) {
this.friend = friend;
firstName.setText(friend.getFirstName());
lastName.setText(friend.getLastName());
}
@FXML
void onSave() {
// if required, you could do some validation on the data before saving here.
friend.setFirstName(firstName.getText());
friend.setLastName(lastName.getText());
saved = true;
getMyStage().close();
}
@FXML
void onCancel() {
getMyStage().close();
}
public boolean isSaved() {
return saved;
}
private Stage getMyStage() {
return (Stage) save.getScene().getWindow();
}
}
friends.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.friends.controllers.FriendsController">
<right>
<VBox spacing="10.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="add" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#addFriend" text="Add" />
<Button fx:id="edit" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#editFriend" text="Edit" />
<Button fx:id="remove" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#removeFriend" text="Remove" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
</right>
<center>
<TableView fx:id="friendsTable" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<columns>
<TableColumn fx:id="firstNameColumn" prefWidth="75.0" text="First name" />
<TableColumn fx:id="lastNameColumn" prefWidth="75.0" text="Last name" />
</columns>
</TableView>
</center>
<bottom>
<Label fx:id="status" maxWidth="1.7976931348623157E308" BorderPane.alignment="CENTER" />
</bottom>
</BorderPane>
friend.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<BorderPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.friends.controllers.FriendController">
<center>
<GridPane hgap="10.0" vgap="5.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="First Name" />
<Label text="Last Name" GridPane.rowIndex="1" />
<TextField fx:id="firstName" GridPane.columnIndex="1" />
<TextField fx:id="lastName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
</GridPane>
</center>
<bottom>
<ButtonBar BorderPane.alignment="CENTER">
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
<buttons>
<Button fx:id="save" mnemonicParsing="false" onAction="#onSave" text="Save" />
<Button fx:id="cancel" mnemonicParsing="false" onAction="#onCancel" text="Cancel" />
</buttons>
</ButtonBar>
</bottom>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</BorderPane>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>friends</artifactId>
<version>1.0-SNAPSHOT</version>
<name>friends</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>21.0.1</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Notes on the usage of the Model class design used in this example
With this example, the Model class with the observable list isn't strictly required. You could just rely on the default list that is provided by the TableView, or one that you create and provide directly in the controller backing the TableView. However, I provided the model code so you could see how such a setup would work and the model can be used for other tasks.
Examples of tasks the observable model could be used for are:
The observable model can be extended to add additional application data and fields in the form of further observable lists and properties, but that wasn't necessary for this simple application. A larger application can have a larger observable model.
If using a dependency injection framework, such as Spring, the model can be represented as an injectable bean. Then the injection framework can inject into any component known by the injection framework (e.g. into FXML controllers via a configured FXML controller factory).
Other design notes
The example could have made use of the built-in JavaFX Dialog classes, but instead uses Stages created manually for the dialogs.