0

I have a TableView<Marriage> in controller's class:

public class MarriageTableView extends AbstractTableViewController {

    @FXML
    private TableView<Marriage> tableView;

    @FXML
    private TableColumn<Groom, String> groomFirstNameColumn,  groomLastNameColumn;

    @FXML
    private TableColumn<Bride, String> brideFirstNameColumn, brideLastNameColumn;

    @FXML
    private TableColumn<Marriage, LocalDate> marriageDateColumn;
    
    @FXML
    void initialize() {
        this.groomFirstNameColumn.setCellValueFactory(new PropertyValueFactory<Groom, String>("firstName")); // <- Exception here
        this.groomLastNameColumn.setCellValueFactory(new PropertyValueFactory<Groom, String>("lastName"));
        this.brideFirstNameColumn.setCellValueFactory(new PropertyValueFactory<Bride, String>("firstName"));
        this.brideLastNameColumn.setCellValueFactory(new PropertyValueFactory<Bride, String>("lastName"));
        this.marriageDateColumn.setCellValueFactory(new PropertyValueFactory<Marriage, LocalDate>("marriageDate"));
    }

In initialize method, I am trying to set CellValueFactory for each table column, so I can show Marriage object in my tableview.

However Marriage object consists of another objects of type Groom and Bride from which I am trying to show some values.

public class Marriage {
    private Groom groom;
    private Bride bride;
    private LocalDate marriageDate;
}

public class Groom:
{
    private String firstName;
    private String lastName;
}

public class Bride:
{
    private String firstName;
    private String lastName;
}

Exception I get:

java.lang.IllegalStateException: Cannot read from unreadable property firstName

How to set CellValueFactory or edit TableColumn's generic parameters, so I can show properties of nested objects?

0009laH
  • 1,960
  • 13
  • 27
Wortig
  • 963
  • 2
  • 11
  • 37
  • [mcve] please (including the complete stacktrace, fxml, application, complete data class and controller) .. that said: wondering how the snippets you are showing are even compiling, having multiple (first) types on the columns – kleopatra Jul 04 '21 at 10:20
  • @kleopatra What do you mean by even compiling? I can compile with no problem. I do not know what multiple (first) types on the columns are you talking about. TableColumn takes two generic parameters: https://openjfx.io/javadoc/16/javafx.controls/javafx/scene/control/TableColumn.html. Anyway it has been closed. – Wortig Jul 04 '21 at 10:34
  • @kleopatra I probably get what you mean by that -> having and as well as as first type parameter in Table Column. Well... for compiler it is no problem. Just normal declaration of private field of type with generic parameters. – Wortig Jul 04 '21 at 10:40
  • meant the first generic type of the column: columns in a `TableView` should all be `TableColumn` - you mix `TableColumn` with `TableColumn` – kleopatra Jul 04 '21 at 10:43
  • it's wrong, nonetheless ;) – kleopatra Jul 04 '21 at 10:43
  • @Abra That is unfortunate, but if you still have the answer and it differs from actual accepted answer here, you can post it somewhere else and give me link. I am interested in another solution. – Wortig Jul 04 '21 at 11:05
  • 2
    Just to note: Your code only compiles because the `FXMLLoader` is the thing adding the columns to the table. It does this via reflection at run-time. Since there are no generics at run-time everything "works" fine (though you're liable to get a `ClassCastException` somewhere). But if you were to have a `TableView` and you tried to add a `TableColumn` to it in Java code then the compilation would fail. The first type argument of the column _must_ match the type argument of the table. – Slaw Jul 04 '21 at 11:18
  • Chat room is here: https://chat.stackoverflow.com/rooms/234493/abra-and-wortig – Abra Jul 04 '21 at 11:21

1 Answers1

2

One option is to use a custom cell implementation that knows how to display the desired data of Groom and Bride. For example:

TableColumn<Marriage, Groom> groomCol = ...;
groomCol.setCellValueFactory(new PropertyValueFactory<>("groom"));
groomCol.setCellFactory(tc -> new TableCell<>() {

  @Override
  protected void updateItem(Groom item, boolean empty) {
    super.updateItem(item, empty); // must be called
    if (empty || item == null) {
      setText(null);
    } else {
      // replace with desired format
      setText(item.getFirstName() + " " + item.getLastName());
    }
  }
});

Note that the first type argument of the TableColumn must match the type argument of the TableView (Marriage in this case).


As an aside, do you really need both a Groom and Bride class? They seem to have the same information and could possibly be combined into a single Person class.


As another aside, you should avoid PropertyValueFactory if you can. It was designed in an era before lambda expressions as a way to make code more concise. But with lambda expressions you can do the same but with compile-time safety (and you avoid reflection). Though note this works best if you expose your model's properties as JavaFX properties. For example:

// model class
public class Marriage {
  private final ObjectProperty<Groom> groom = new SimpleObjectProperty<>(this, "groom");
  public final void setGroom(Groom groom) { this.groom.set(groom); }
  public final Groom getGroom() { return groom.get(); }
  public final ObjectProperty<Groom> groomProperty() { return groom; }

  // other properties...
}
// table column configuration
TableColumn<Marriage, Groom> groomCol = ...;
groomCol.setCellValueFactory(data -> data.getValue().groomProperty());
groomCol.setCellFactory( ... );
Slaw
  • 37,820
  • 8
  • 53
  • 80