TableView
A ListView shows one line per element. When a note has several fields, a TableView is clearer: each element is a row, each field is a column. The table works with the same ObservableList, so almost all the code from the previous lesson stays: we replace ListView with TableView and describe the columns.
A table instead of a list
// src/main/java/com/example/notes/App.java — class fields
private final ObservableList<Note> notes = FXCollections.observableArrayList();
private final TableView<Note> table = new TableView<>(notes);The ListCell cell factory is no longer needed. Everywhere listView was used, it is now table: the table has the same selection model, table.getSelectionModel().
Columns
A TableColumn has two type parameters: the row type (Note) and the cell value type (String). The constructor takes the header text, and the cell value factory says what to show in a cell:
// src/main/java/com/example/notes/App.java — part of start()
TableColumn<Note, String> titleColumn = new TableColumn<>("Title");
titleColumn.setCellValueFactory(cell -> new SimpleStringProperty(cell.getValue().title()));
TableColumn<Note, String> categoryColumn = new TableColumn<>("Category");
categoryColumn.setCellValueFactory(cell -> new SimpleStringProperty(cell.getValue().category()));
TableColumn<Note, String> importantColumn = new TableColumn<>("Important");
importantColumn.setCellValueFactory(
cell -> new SimpleStringProperty(cell.getValue().important() ? "★" : ""));
table.getColumns().add(titleColumn);
table.getColumns().add(categoryColumn);
table.getColumns().add(importantColumn);cell.getValue() is the note of this row. The factory has to return a property, so the string is wrapped in a SimpleStringProperty.
Width and an empty table
// src/main/java/com/example/notes/App.java — part of start()
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
table.setPlaceholder(new Label("No notes yet"));| Policy | What it does |
|---|---|
UNCONSTRAINED_RESIZE_POLICY | The default: columns keep their width, empty space may remain on the right |
CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN | Columns fill the whole width, the last one gets the rest |
CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS | The rest is shared among all columns |
setPlaceholder sets the label the table shows when it has no rows.
The window size
A table needs more room than a list. Let's widen the left panel and the window:
// src/main/java/com/example/notes/App.java — part of start()
VBox listPane = new VBox(8, table, countLabel);
listPane.setPrefWidth(320);
VBox.setVgrow(table, Priority.ALWAYS);
// ...
stage.setScene(new Scene(root, 800, 480));Trying it out
Run the application. On the left is a table with the "Title", "Category" and "Important" columns: the first row holds "Buy milk", "Home" and a star, the second "Call Oleg" and "Work". The form is on the right as before. Add "Read a book" with the "Ideas" category and a third row appears. A click on a column header sorts the table by it, a second click reverses the order, a third one removes the sorting.
Common mistake
Older examples fill a column like this: new PropertyValueFactory<>("title"). That class looks for a getTitle() method, but a record's method is called title(). The column stays empty, and the terminal shows WARNING: Can not retrieve property 'title' in PropertyValueFactory and java.lang.IllegalStateException: Cannot read from unreadable property title. For records use a lambda, as in this lesson.