Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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"));
PolicyWhat it does
UNCONSTRAINED_RESIZE_POLICYThe default: columns keep their width, empty space may remain on the right
CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMNColumns fill the whole width, the last one gets the rest
CONSTRAINED_RESIZE_POLICY_ALL_COLUMNSThe 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.

Thanks for reading!

Donate to the site — we work hard for you.