Updating TableView from another FXML/Controller

108 Views Asked by At

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?

1

There are 1 best solutions below

0
jewelsea On BEST ANSWER

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:

  • Sharing data with other components of your application (e.g. database or persistence layers, or other GUI controllers)
  • Initializing some test data into the model (as demonstrated in this example).

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.