javajavafxmodel-view-controllertableview

Dynamic TableView creating with JavaFX


I'm new. Let's say I want to create a TableView dinamycally based on someString with query like this

TableView<???> table = new TableView<>();
ResultSet rs = statement.executeQuery("SELECT * FROM %s".formatted(someString));

So I will have various tables with various object types and therefore properties and columns

ResultSetMetaData meta = rs.getMetaData();
for (int i = 0; i < meta.getColumnCount(); i++) {
    TableColumn<???, String> column = new TableColumn<>(meta.getColumnLabel(i + 1));
    table.getColumns().add(column);
    column.setCellValueFactory(data -> data.getValue().???dynamicPropertyBasedOn[i]);
}

while (rs.next()) {........}

How do I setCellValueFactory() not knowing Object type and exact number of columns beforehand?

I have models for my database tables, for basic example

Student.java

public class Student {
    private final StringProperty lastName;
    private final StringProperty firstName;
    private final IntegerProperty age;
    ...........
}

Schedule.java

public class Schedule {
    private final ObjectProperty<LocalDate> date;
    private final BooleanProperty completed;
    ...........
}

So I need to create TableViews of various Objects with one function.. Is it possible anyhow?


Solution

  • If you're creating a GUI with the purpose of displaying specific, known data types then I recommend you create the tables in the usual way. That means create a model class for the data that exposes JavaFX properties and write the code that configures the table view to work with that model class. At most, you could create a framework that generates a table view based on the model class. How complex the framework is depends on your exact needs. In general though, such a framework would likely rely on reflection and custom annotations (for configuration).

    However, if you're creating a GUI that is supposed to work with an arbitrary database that isn't known at build time, then relying on ResultSetMetaData is a good approach. But in this case you won't have specialized model classes. Instead, you'd use a single general model class, if not simply just an array or list of objects. At the most basic level, it could look something like this:

    public TableView<?> toTableView(ResultSet queryResult) throws SQLException {
      var table = new TableView<Object[]>();
    
      var metadata = queryResult.getMetaData();
      int columnCount = metadata.getColumnCount();
      for (int i = 1; i <= columnCount; i++) {
        final int idx = i;
        var column = new TableColumn<Object[], Object>(metadata.getColumnLabel(idx));
        column.setCellValueFactory(data -> new SimpleObjectProperty<>(data.getValue()[idx - 1]));
        table.getColumns().add(column);
      }
    
      var items = table.getItems();
      while (queryResult.next()) {
        var row = new Object[columnCount];
        for (int i = 1; i <= columnCount; i++) {
          row[i - 1] = queryResult.getObject(i);
        }
        items.add(row);
      }
    
      return table;
    }
    

    That will display every column value using its Object::toString method. If you want to get more detailed, then you need to make use of methods like getColumnType, getScale, getPrecision, and isCurrency from ResultSetMetaData. You would set the cell factories (to customize the display) based on the results of those methods.