javafxjavafx-8fxml

Updating TableView from another FXML/Controller


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?


Solution

  • Here is an example performing create, update, and delete operations on a table of data utilizing separate dialog windows and FXML.

    adding

    added

    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.