0

I have a TableView that contains entries from a database and a separate window for entering a new entry with a separate controller. The problem is that when I add a record to the database, the table scene controller doesn't know that there has been a change and the TableView is only updated after the application is restarted. How to create this connection between the controllers so that the TableView is updated immediately?

Main class.

public class ExampleApplication extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("tableView.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 700, 400);
        stage.setTitle("ExampleApp");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();

    }
}

Scene controller with a table where entries are displayed.

public class TableController {

    @FXML
    private Button addEntryButton;

    @FXML
    private TableColumn<userEntry, String> entryColumn;

    @FXML
    private TableView<userEntry> table;

    ObservableList<userEntry> userEntries = FXCollections.observableArrayList();

    @FXML
    private void initialize() {
        table.getItems().clear();
        getAllEntriesFromDbToList();
        entryColumn.setCellValueFactory(new PropertyValueFactory<userEntry, String>("entryName"));
        table.setItems(userEntries);

        addEntryButton.setOnAction(event -> {
            openNewScene("/com/example/addNewEntryDialogue.fxml");
        });
    }

    private void getAllEntriesFromDbToList() {
        ResultSet rs = new DatabaseHandler().getEntriesTableFromDb();
        try {
            while (rs.next()) {
                userEntries.add(new userEntry(rs.getString("entryName")
                ));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    private void openNewScene(String window) {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource(window));

        try {
            loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }

        Parent root = loader.getRoot();
        Stage stage = new Stage();
        stage.setScene(new Scene(root));
        stage.show();
    }

}

Dialog window controller for adding a new entry.

public class DialogueController {

    @FXML
    private Button inputNewEntryCancelButton;

    @FXML
    private Button inputNewEntryOkButton;

    @FXML
    private TextField inputNewEntryTextField;

    @FXML
    void initialize() {
        inputNewEntryOkButton.setOnAction(event -> {
            addNewEntryToDbTable(inputNewEntryTextField.getText());
            ((Node) event.getSource()).getScene().getWindow().hide();
        });

        inputNewEntryCancelButton.setOnAction(event -> {
            ((Node) event.getSource()).getScene().getWindow().hide();
        });
    }

    private void addNewEntryToDbTable(String entryName) {
        DatabaseHandler dbHandler = new DatabaseHandler();
        userEntry newEntry = new userEntry(entryName);
        dbHandler.addEntryToDb(newEntry);
    }
}

Model. Entry class.

public class userEntry {

    private String entryName;

    public userEntry(String entryName) {
        this.entryName = entryName;
    }
    
    public String getEntryName() {
        return entryName;
    }

    public void setEntryName(String entryName) {
        this.entryName = entryName;
    }
}

Model. Data base handler class.

public class DatabaseHandler extends Configs {

    Connection dbConnection;

    public Connection getDbConnection() throws ClassNotFoundException, SQLException {
        String connectionString = "jdbc:mysql://" + dbHost + ":" + dbPort + "/" + dbName;
        Class.forName("com.mysql.cj.jdbc.Driver");
        dbConnection = DriverManager.getConnection(connectionString, dbUser, dbPass);

        return dbConnection;
    }

    public void addEntryToDb(userEntry task) {
        String insert = "INSERT INTO " + Const.ENTRIES_TABLE + "(" +
                Const.ENTRIES_ENTRY_NAME + ")" +
                "VALUES(?)";
        try {
            PreparedStatement prSt = getDbConnection().prepareStatement(insert);
            prSt.setString(1, task.getEntryName());

            prSt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    public ResultSet getEntriesTableFromDb() {
        ResultSet resSet = null;
        String select = "SELECT * FROM " + Const.ENTRIES_TABLE;
        try {
            resSet = getDbConnection().createStatement().executeQuery(select);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return resSet;
    }
}

View. Table scene FXML. (tableView.fxml)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane prefHeight="386.0" prefWidth="248.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/18" fx:controller="com.example.tableviewtesting.TableController">
   <opaqueInsets>
      <Insets />
   </opaqueInsets>
   <bottom>
        <TableView fx:id="table" maxHeight="-Infinity" prefHeight="329.0" prefWidth="248.0" BorderPane.alignment="CENTER">
            <columns>
                <TableColumn fx:id="entryColumn" prefWidth="247.0" text="Entries" />
            </columns>
        </TableView>
   </bottom>
   <top>
      <Button fx:id="addEntryButton" mnemonicParsing="false" text="Add entry">
         <BorderPane.margin>
            <Insets left="10.0" top="10.0" />
         </BorderPane.margin>
      </Button>
   </top>
</BorderPane>

View. Dialog scene FXML. (addNewEntryDialogue.fxml)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="100.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.tableviewtesting.DialogueController">
   <children>
      <Label alignment="CENTER" layoutX="8.0" layoutY="18.0" prefHeight="36.0" prefWidth="70.0" text="Entry name:" />
      <TextField fx:id="inputNewEntryTextField" layoutX="82.0" layoutY="16.0" prefHeight="40.0" prefWidth="208.0" />
      <Button fx:id="inputNewEntryCancelButton" layoutX="156.0" layoutY="65.0" mnemonicParsing="false" prefHeight="25.0" prefWidth="134.0" text="Cancel" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="156.0" AnchorPane.rightAnchor="10.0" />
      <Button fx:id="inputNewEntryOkButton" layoutX="10.0" layoutY="70.0" mnemonicParsing="false" prefHeight="25.0" prefWidth="134.0" text="Create new entry" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="156.0" />
   </children>
</AnchorPane>
filippkos
  • 11
  • 1
  • 4
  • after successfully adding a record, return it as a result and add it to the table. there are other approaches, but for a start you should learn how to use dialogues. – mr mcwolf May 22 '22 at 13:17
  • Another way is to pass a reference to the table's items list [to the dialog controller](https://stackoverflow.com/questions/61531317/how-do-i-determine-the-correct-path-for-fxml-files-css-files-images-and-other), and then add the new item to that list in the `addNewEntryToDbTable()` method. – James_D May 22 '22 at 18:45
  • @mr mcwolf It worked. After updating the database, I inserted a method that takes the last record from the database and adds it to the list. Even though it looks like a dirty hack, I'm very happy. Thanks! – filippkos Jun 02 '22 at 11:57
  • @kleopatra Thanks for the clarification. I was trying to follow the **C** rule. I thought the absence of a database would distort the essence of my question, because if I used a list instead of a base, then it could simply be passed from controller to controller. – filippkos Jun 02 '22 at 12:09
  • @James_D In this case, the list must have the final modifier so that it is the same instance of it. Right? – filippkos Jun 02 '22 at 12:26
  • @filippkos No, that’s not what `final` means. – James_D Jun 02 '22 at 12:28
  • @James_D I'm sorry, I meant static. – filippkos Jun 02 '22 at 12:59
  • @filippkos No, why would you make it `static`, if you’re passing a reference to the other controller? You don’t even need an instance variable. – James_D Jun 02 '22 at 13:16
  • @James_D Because I don't know how to pass a controller field to another controller without creating an instance of one controller in another and have the contents of the field also be passed to another controller. It after all also meant - to pass the reference? – filippkos Jun 02 '22 at 18:22
  • @filippkos Sorry: I had the wrong link in my original comment. I meant to link [this question](https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml) – James_D Jun 02 '22 at 18:51

1 Answers1

1

You did not produce an MCVE, so I am going to try to guess based on your limited code.

Here is a method that deletes a Person from an SQLite database.

The following code accepts the person that needs to be deleted in the DB and returns a boolean based on if the person was successfully deleted from the DB or not.

dBHandler.deletePerson(person)

The following code demonstrates using the boolean return value to determine what should happen based on if true or false is returned.

if (dBHandler.deletePerson(person)) {
    //delete the `Person` from the `TableView`.     
} else {
    //Alert the user that the `Person` was not deleted from the DB.
    //Do not delete the `Person` from the `TableView`.
}

Actual Code Snippet

@FXML
private void handleBtnDeletePerson(ActionEvent actionEvent) {
    Person person = tvMain.getSelectionModel().getSelectedItem();

    if (dBHandler.deletePerson(person)) {
        //Update TableView
        tvMain.getItems().remove(person); //Remove Person from the TableView
        lblLastAction.setText(person.getFirstName() + " deleted!");
    } else {
        lblLastAction.setText(person.getFirstName() + " failed to delete!");
    }

}

Full Code Link: https://github.com/sedj601/SQLitePersonTableViewExample

If you have a long-running task, see the following code: https://github.com/sedj601/SQLitePersonTableViewExampleUsingTask.

Warning: this is old code and may be lacking! Hopefully, the ideas are sound, though!

SedJ601
  • 12,173
  • 3
  • 41
  • 59