JavaFX TableView

Jakob Jenkov
Last update: 2021-01-07

The JavaFX TableView enables you to display table views inside your JavaFX applications. The JavaFX TableView is represented by the class javafx.scene.control.TableView . Here is a screenshot of a JavaFX TableView:

A JavaFX TableView screenshot

Classes Related to TableView

The JavaFX TableView class uses a set of related classes to do its job. These classes are:

  • TableColumn
  • TableRow
  • TableCell
  • TablePosition
  • TableViewFocusModel
  • TableViewSelectionModel

What exactly these classes do will be covered in the corresponding sections later in this tutorial.

JavaFX TableView Example

Here is a full, but simple JavaFX TableView code example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableViewExample extends Application {

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

  @Override
  public void start(Stage primaryStage) {

    TableView tableView = new TableView();

    TableColumn<Person, String> column1 = 
new TableColumn<>("First Name");
column1.setCellValueFactory(
new PropertyValueFactory<>("firstName")); TableColumn<Person, String> column2 =
new TableColumn<>("Last Name");
column2.setCellValueFactory(
new PropertyValueFactory<>("lastName")); tableView.getColumns().add(column1); tableView.getColumns().add(column2); tableView.getItems().add(
new Person("John", "Doe")); tableView.getItems().add(
new Person("Jane", "Deer")); VBox vbox = new VBox(tableView); Scene scene = new Scene(vbox); primaryStage.setScene(scene); primaryStage.show(); } }

Here is the Person class used in this example:

public class Person {

    private String firstName = null;
    private String lastName = null;

    public Person() {
    }

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Create a TableView

In order to use a JavaFX TableView component you must first create a TableView instance. Here is an example of creating a JavaFX TableView instance:

TableView tableView = new TableView();

Add TableColumn to the TableView

Having created a TableView you need to add one or more TableColumn instances to the TableView instance. A TableColumn represents a vertical column of values. Each value is displayed on its own row, and is typically extracted from the list of objects being displayed in the TableView. Here is an example of adding two TableColumn instances to a JavaFX TableView instance:

TableView tableView = new TableView();

TableColumn<Person, String> firstNameColumn = 
TableColumn<>("First Name"); firstNameColumn.setCellValueFactory(
new PropertyValueFactory<>("firstName")); TableColumn<Person, String> lastNameColumn =
new TableColumn<>("Last Name"); lastNameColumn.setCellValueFactory(
new PropertyValueFactory<>("lastName"));

Notice the Java String parameter passed to the constructor of the TableColumn class. This string will be displayed as the column header above the column. You can see an example of such a column header title in the screenshot at the top of this page.

TableColumn Cell Value Factory

A TableColumn must have a cell value factory set on it. The cell value factory extracts the value to be displayed in each cell (on each row) in the column. In the example above a PropertyValueFactory is used. The PropertyValueFactory factory can extract a property value (field value) from a Java object. The name of the property is passed as a parameter to the PropertyValueFactory constructor, like this:

PropertyValueFactory factory = 
new PropertyValueFactory<>("firstName");

The property name firstName will match the getter getter method getFirstName() of the Person objects which contain the values are displayed on each row.

In the example shown earlier, a second PropertyValueFactory is set on the second TableColumn instance. The property name passed to the second PropertyValueFactory is lastName, which will match the getter method getLastName() of the Person class.

Add Data to TableView

Once you have added TableColumn instances to the JavaFX TableView, you can add the data to be displayed to the TableView. The data is typically contained in a list of regular Java objects (POJOs). Here is an example of adding two Person objects (class shown earlier in this JavaFX TableView tutorial) to a TableView :

tableView.getItems().add(new Person("John", "Doe"));
tableView.getItems().add(new Person("Jane", "Deer"));

Using Maps as Data Items

It is possible to use standard Java Map instances for your data items instead of creating a specific class for them (as the Person class in earlier examples).

To use a Map as a data item the TableColumn instances attached to the TableView need to use a special cell value factory called a MapValueFactory. Here is an example of how configuring two TableColumn instances to display first name and last name extracted from a Map looks:

TableColumn<Map, String> firstNameColumn = new TableColumn<>("firstName");
firstNameColumn.setCellValueFactory(new MapValueFactory<>("firstName"));

TableColumn<Map, String> lastNameColumn = new TableColumn<>("lastName");
lastNameColumn.setCellValueFactory(new MapValueFactory<>("lastName"));

With these TableColumn configurations you can use Map instances as data items. Here is a full example showing how to use Map instances as data items:

TableView tableView = new TableView();

TableColumn<Map, String> firstNameColumn = new TableColumn<>("firstName");
firstNameColumn.setCellValueFactory(new MapValueFactory<>("firstName"));

TableColumn<Map, String> lastNameColumn = new TableColumn<>("lastName");
lastNameColumn.setCellValueFactory(new MapValueFactory<>("lastName"));

tableView.getColumns().add(firstNameColumn);
tableView.getColumns().add(lastNameColumn);


ObservableList<Map<String, Object>> items =
    FXCollections.<Map<String, Object>>observableArrayList();

Map<String, Object> item1 = new HashMap<>();
item1.put("firstName", "Randall");
item1.put("lastName" , "Kovic");

items.add(item1);

Map<String, Object> item2 = new HashMap<>();
item2.put("firstName", "Irmelin");
item2.put("lastName" , "Satoshi");

items.add(item2);

tableView.getItems().addAll(items);

Set Placeholder When No Rows to Display

You can set a placeholder to be displayed when the JavaFX TableView has no rows to display. The placeholder must be an instance of the JavaFX Node class, which most (if not all) JavaFX controls are. Thus, you can use an JavaFX ImageView or JavaFX Label as placeholder. Here is an example of using a Label as placeholder in a JavaFX TableView :

tableView.setPlaceholder(
new Label("No rows to display"));

And here is how the corresponding TableView looks with the placeholder displayed:

A JavaFX TableView with a placeholder displayed when no rows to display.

TableView Selection Model

The JavaFX TableView component has an internal SelectionModel which is used to either read what rows and / or cells the user has selected, or to select rows and cells programmatically. In the following sections I will take a closer look at the JavaFX TableView SelectionModel.

Obtain TableView SelectionModel Instance

To obtain the JavaFX TableView SelectionModel you must call its getSelectionModel() method. Here is an example of obtaining the SelectionModel from a JavaFX TableView:

TableViewSelectionModel selectionModel = 
tableView.getSelectionModel();

The TableViewSelectionModel class can have a generic type set on it. Typically that type will be the same as was used for the data displayed in the TableView. Here is an example of declaring a generic type on the TableViewSelectionModel when obtaining it:

TableViewSelectionModel<Person> selectionModel = 
tableView.getSelectionModel();

Set Selection Mode

You can set the selection mode of the TableView TableViewSelectionModel using its setSelectionMode() method. You can choose whether it should be possible to select only one row, or multiple rows, or even individual cells. Here is an example of setting the selection mode of a TableViewSelectionModel:

// set selection mode to only 1 row
selectionModel.setSelectionMode(
SelectionMode.SINGLE); // set selection mode to multiple rows selectionModel.setSelectionMode(
SelectionMode.MULTIPLE);

The first line sets the selection mode to only allow a single row to be selected at a time. This is the default setting of the TableViewSelectionModel, by the way.

The second line sets the selection mode to allow the selection of multiple rows. The user will need to hold down the SHIFT or CTRL key while selecting rows to select more than one row.

Get Selected Rows

To obtain a list of the selected row items of a JavaFX TableView, you call the SelectionModel getSelectedItems() method. The list returned is read-only. Here is an example of obtaining a list of the selected rows from a JavaFX TableView SelectionModel:

ObservableList selectedItems = 
selectionModel.getSelectedItems();

If the TableViewSelectionModel has a generic type set on it, the ObservableList returned from the getSelectedItems() will have the same generic type. Here is an example:

ObservableList<Person> selectedItems = 
selectionModel.getSelectedItems();

Get Selected Indices

You can also just obtain a list of the indices of the selected rows, instead of the selected items themselves. Here is an example of obtaining a list of the indices of selected rows from a TableViewSelectionModel:

ObservableList<Integer> selectedIndices = 
selectionModel.getSelectedIndices();

Clear Selection

You can clear all selected rows and cells using the TableViewSelectionModel clearSelection() method. Here is an example of clearing all selected rows or cells of a TableView via the TableViewSelectionModel clearSelection() method:

selectionModel.clearSelection();

Listening for Selection Changes

It is possible to listen for selection changes in the TableViewSelectionModel. To do so, you must attach a listener to one of the ObservableList's returned by either getSelectedItems() or getSelectedIndices(). Which of the two lists you should use depends on whether you need to obtain the selected items or selected indices when selection changes. Here is an example of attaching a change listener to the the ObservableList returned by getSelectedItems():

ObservableList<Person> selectedItems = 
selectionModel.getSelectedItems(); selectedItems.addListener(
new ListChangeListener<Person>() { @Override public void onChanged(
Change<? extends Person> change) { System.out.println(
"Selection changed: " + change.getList()); } })

Select Rows Programmatically

It is possible to select rows programmatically in a JavaFX TableView. You do so via the TableViewSelectionModel object's many selection methods. To select a row with a specific index you can use the select(int) method. Here is an example of selecting a single row with a given index in a JavaFX TableView:

selectionModel.select(1);

This example selects the row with index 1. Remember that row indices starts from 0 .

Nested Columns

The JavaFX TableView supports nesting of the TableColumn instances you add to it. By nesting columns is simply meant that a table column named "Customer" can be subdivided into two nested columns - e.g. "Customer No" and "Name". And the "Name" column can be subdivided again into "First Name" and "Last Name". Here is a JavaFX TableView nested column example showing that:

A JavaFX TableView with nested columns in 3 nesting levels.

The TableColumn instances lowest in the nesting levels are called leaf columns. Only leaf columns can show data. Thus, you only add cell value factories to the leaf TableColumn instances. The TableColumn instances higher up in the nesting levels are primarily there as a visual effect.

Here is the code for the above JavaFX TableView nested column example:

TableView tableView = new TableView();
    
TableColumn<Customer, String> customer   = 
new TableColumn<>("Customer"); TableColumn<Customer, String> customerId =
new TableColumn<>("Customer No"); TableColumn<Customer, String> name =
new TableColumn<>("Name"); TableColumn<Customer, String> firstName =
new TableColumn<>("First Name"); TableColumn<Customer, String> lastName =
new TableColumn<>("Last Name"); name .getColumns().addAll(firstName, lastName); customer .getColumns().addAll(customerId, name); tableView.getColumns().addAll(customer); customerId.setCellValueFactory(
new PropertyValueFactory<>("customerNo")); firstName .setCellValueFactory(
new PropertyValueFactory<>("firstName")); lastName .setCellValueFactory(
new PropertyValueFactory<>("lastName")); tableView.getItems().add(
new Customer("007", "Jane", "Deer")); tableView.getItems().add(
new Customer("006", "John", "Doe")); tableView.getItems().add(
new Customer("008", "Mack", "Alamo"));

Visible Leaf Columns

The JavaFX TableView class has a method called c which can return a visible leaf column (TableColumn) of the TableView. You pass the index of the visible leaf column to get as parameter to the getVisibleLeafColumn() method. Here is an example of obtaining the second (index 1) visible leaf column from a JavaFX TableView:

TableColumn<String, Customer> leafColumn =
    tableView.getVisibleLeafColumn(1);

If the above code was executed against the TableView configured in the example in the previous section, the TableColumn returned would be the "First Name" TableColumn.

You can also obtain all visible leaf columns via the TableView getVisibleLeafColumns() .

Hide Columns

The JavaFX TableView enaables you to hide a TableColumn by calling the TableColumn setVisible() passing false as parameter. Here is an example of hiding a TableColumn:

tableColumn.setVisible(false);

To show a hidden column again, simply call setVisible() with true as parameter value. Here is an example of showing a TableColumn:

tableColumn.setVisible(true);

Reorder Columns

The JavaFX TableView enables you to reorder its columns. There are two ways you can recorder its columns.

The first method is to drag a column to a different horizontal position inside the TableView. This is done by the end user - not the developer.

The second method is to change the order of the TableColumn instances in the ObservableList returned by the TableView getColumns() method. This is done programmatically. Here is an example of removing the first column and adding it again as the last column of the TableView:

tableView.getColumns().add(
    tableView.getColumns().remove(0));

Sorting Rows

The JavaFX TableView enables you to sort the rows in the TableView. There are two ways rows can be sorted.

The first way is for the user to click on the TableColumn header cell (where the column title is displayed). This will sort the rows in the TableView after the values of that column. If the user clicks on the same TableColumn cell one more time, the sort order is reversed. And if the user clicks a third time, the sorting on that column is disabled again.

The second way to sort the rows of a TableView is programmatic. You can set the sort order (ascending or descending), set a custom Comparator to compare the values of a TableColumn, set a custom sort icon to be displayed, and even disable sorting completely for a given TableColumn. These options will be explained in more detail below.

Sort Type

You can specify which sort type you want the column to use using the TableColumn setSortType() method. You can set the sort type to either ascending or descending. Here are two examples that show how to set both sort types:

tableColumn.setSortType(TableColumn.SortType.ASCENDING);

tableColumn.setSortType(TableColumn.SortType.DESCENDING);

Disable Sorting for a Column

It is possible to complete disable sorting for a given TableColumn. You do so via the TableColumn setSortable() method. Here is an example of disabling sorting for a given TableColumn:

tableColumn.setSortable(false);

To re-enable sorting for the TableColumn, simply call setSortable() with the value true as parameter - like this:

tableColumn.setSortable(false);

Column Custom Comparator

You can set a custom Comparator interface implementation on a TableColumn. This Comparator implementation will determine how values displayed in that column will get sorted. You set a Comparator on a TableColumn via its setComparator() method. Here is an example of setting a custom Comparator on a TableColumn:

Comparator<String> columnComparator =
    (String v1, String v2) -> {

  return v1.toLowerCase().compareTo(
        v2.toLowerCase());

};

tableColumn.setComparator(columnComparator);

This example first creates a Comparator implementation that compares String objects in their lowercase forms. Then it sets this Comparator on a TableColumn. This Comparator will then determine the sort order of values displayed in that column.

Column Default Comparator

The TableColumn has a default Comparator set on it which can perform some intelligent form of default sorting for you. Sometimes that default Comparator is enough. Here is how the default Comparator compares two values displayed inside the same column:

  1. Checks for null values. A null value is considered less than a non-null value. If both values are null they are considered equal.
  2. If the first value is an object implementing the Comparable interface, then the default Comparator will return value1.compareTo(value2) .
  3. If rule 1 and 2 did not apply to the values - the default Comparator converts the two values into Strings using their .toString() methods, and compares these.

Column Sort Node

The TableColumn class uses a "sort Node" to display the type of sorting currently being applied to that column. The sort Node is displayed inside the column header. The default sort Node is an arrow-like triangle.

You can change the sort Node for a TableColumn via its setSortNode() method, passing a JavaFX Node to be used as sort Node. Here is an example of setting an ImageView as sort Node on a TableColumn:

FileInputStream input = new FileInputStream("images/sort-image.png");
Image image = new Image(input);
ImageView sortImage = new ImageView(image);

tableColumn.setSortNode(sortImage);

Note: The default sort Node used by the TableColumn is rotated 180 degrees when the sort type changes from ascending to descending. However, this rotation does not seem to happen for a custom sort Node. You will probably have to manage the change of sort Node yourself when the sort type is changed.

Column Sort Order

The JavaFX TableView enables you to specify the default sort order of a TableView. The items in the TableView will be sorted according to this order - until the user clicks some TableColumn headers and changes the sort order.

The default sort order consists of an ObservableList of TableColumn instances. Items in the TableView will be sorted after the sorting set on the first TableColumn in this list. If the values of the first TableColumn are equal, the items will be sorted accordinng to the sorting set on the second TableColumn, and if they are equal then the third TableColumn etc.

Here is an example of setting the default sort order:

 tableView.getSortOrder().addAll(lastNameColumn, firstNameColumn);

This example sets the sort order to lastNameColumn first, and then firstNameColumn second.

Note: I am having a bit of challenges seeing the sort order reflected correctly in the items displayed in the TableView when I run my examples - but something happens though. You might have to play a bit with this feature to get it to do as you want!

Trigger Sorting Manually

The JavaFX TableView control contains a method named sort() which when called will trigger sorting of the items in the TableView. Here is an example of caling the TableView sort() method:

tableView.sort();

Sort Events

The JavaFX TableView fires a sort event just before sorting of the items is carried out. You can listen for sort events using the TableView setOnSort() method, passing an EventHandler interface implementation as parameter. Here is an example of listening for sort events on a JavaFX TableView:

tableView.setOnSort((event) -> {
    System.out.println("Sorting items");
});

By the way, if the event listener calls event.consume() - then the sorting will be canceled. Here is how that could look:

tableView.setOnSort((event) -> {
    event.consume();
});

Disable Sorting for TableView

You can disable sorting for the whole TableView either by listening for sort events and consume them (call event.consume() ) or by disabling sorting on all TableColumn instances. There are other ways to disable sorting for the whole TableView too - but these two should suffice.

Virtualized Cell Rendering

The rendering of cells in a JavaFX TableView is virtualized. That means, that the TableView will only create cell rendering objects for the rows / columns that are visible - not for all rows in the backing data set. Thus, if a TableView contains 1,000 rows, but only 10 of them are visible at a time, then the TableView will only create cell rendering objects for the 10 rows that are visible - not for all 1,000 rows. As the user scrolls through the data in the data set displayed in the TableView, the already created cell rendering objects are reused to display the rows that become visible. This way, the TableView can handle very large data sets with less memory overhead from cell rendering objects.

Custom Cell Rendering

The JavaFX TableView enables you to customize the rendering of the cells displayed in the TableView. Each TableColumn added to the TableView has one cell per visible row in the TableView. That means, that if the TableView has 10 visible rows, and 3 columns, then each column (TableColumn) has 10 cells - for a grand total of 30 cells.

As mentioned, the cell count is determined by the number of visible rows and columns (visible cells). It doesn't matter if there are 1000 data items (rows) in the items list of the TableView. It only matters how many rows are visible at a given time. The visible cell objects will be reused to display the values of whatever rows are visible at any given time. This design saves a lot of cell object creation compared to creating a cell object for each column of each row (data item).

To customize the cell rendering you must set a cell factory on the TableColumn for which you want to customize the cell rendering. You set a cell factory via the TableColumn setCellFactory() method. The setCellFactory() method takes a Callback interface implementation as parameter. The Callback implementation creates TableCell instances. Each TableCell instance corresponds to one visible cell in the TableView. The Callback implementation will be called one time per TableCell to create, so the Callback implementation does not need to know how many visible cells there are in the TableView.

To better understand how cell factories work, let us look at an example of setting a cell factory on a TableColumn:

customerColumn.setCellFactory((tableColumn) -> {
    TableCell<Customer, String> tableCell = new TableCell<>() {
        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);

            this.setText(null);
            this.setGraphic(null);

            if(!empty){
                this.setText(item.toUpperCase());
            }
        }
    };

    return tableCell;
});

This example calls the setCellFactory() method with a Callback implementation as a Java Lambda Expression.

The Callback implementation creates a TableCell and returns it. The TableCell created is an anonymous subclass of the TableCell class, where the updateItem() method is overridden. The updateItem() method on the TableCell subclass is called every time the TableCell is to show the column value for a new item (row). That is why the updateItem() method first calls setText(null) and setGraphic(null) - to clear whatever text and graphics the TableCell was showing before.

After clearing the values from the previous item (row), the new value is set - if the cell is not empty that is. The second parameter passed to updateItem() is a boolean which will have the value true if the cell is empty. If the cell is empty the TableCell should not show any value.

A TableCell is rendered similarly to a JavaFX Label - meaning it can show both a graphic and a text. To show a graphic the TableCell updateItem() call the setGraphic() method with a JavaFX Node object as parameter. To show a text call setText() with a String as parameter. The TableCell can show both graphic and text at the same time.

Editable TableView

It is possible to make a JavaFX TableView editable. Making a JavaFX TableView editable requires a few steps. First, you must call the setEditable() method of the TableView, passing a value of true as parameter. Here is how that looks:

tableView.setEditable(true);

Second, you must set a different cell renderer on the TableColumn's you want to be editable. JavaFX comes with 4 built-in cell renderers you can use:

  • TextFieldTableCell
  • CheckBoxTableCell
  • ChoiceBoxTableCell
  • ComboBoxTableCell

The TextFieldTableCell cell renderer is used to enable editing of a cell value using a TextField. The CheckBoxTableCell cell renderer is used to enable editing of a cell value using a CheckBox. The ChoiceBoxTableCell cell renderer is used to enable editing of a cell value using a ChoiceBox. The ComboBoxTableCell cell renderer is used to enable editing of a cell value using a ComboBox.

Here is an example of how to use these cell renderers:

TableView tableView = new TableView();
tableView.setEditable(true);

TableColumn<Person, String> column1 = new TableColumn<>("First Name");
column1.setCellValueFactory(new PropertyValueFactory<>("firstName"));

TableColumn<Person, String> column2 = new TableColumn<>("Last Name");
column2.setCellValueFactory(new PropertyValueFactory<>("lastName"));
column2.setCellFactory(TextFieldTableCell.<Person>forTableColumn());

TableColumn<Person, String> column3 = new TableColumn<>("Category");
column3.setCellValueFactory(new PropertyValueFactory<>("category"));
column3.setCellFactory(ComboBoxTableCell.<Person, String>forTableColumn("Category 1", "Category 2"));

TableColumn<Person, Boolean> column4 = new TableColumn<>("Is XYZ");
column4.setCellValueFactory( cellData -> new ReadOnlyBooleanWrapper(cellData.getValue().getIsXyz()));
column4.setCellFactory(CheckBoxTableCell.<Person>forTableColumn(column4));


tableView.getColumns().add(column1);
tableView.getColumns().add(column2);
tableView.getColumns().add(column3);
tableView.getColumns().add(column4);

tableView.getItems().add(new Person("John"  , "Doe"  , null, false));
tableView.getItems().add(new Person("Jane"  , "Deer" , "Category 1", true));
tableView.getItems().add(new Person("Dinesh", "Gupta", "Category 2", true));

The Person class used in this example looks like this:

public class Person {

    private String firstName = null;
    private String lastName = null;

    private String category = null;

    private boolean isXyz = true;

    public Person() {
    }

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Person(String firstName, String lastName, String category, boolean isXyz) {
        this.firstName = firstName;
        this.lastName  = lastName;
        this.category  = category;
        this.isXyz     = isXyz;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String toString() {
        return "{ firstName: '" + this.firstName + ", lastName: " + this.lastName + " }";
    }


    public String getCategory() {
        return this.category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public Boolean getIsXyz() {
        return this.isXyz;
    }

    public void setIsXyz(Boolean isXyz) {
        this.isXyz = isXyz;
    }
}

Jakob Jenkov

Featured Videos

Java ConcurrentMap + ConcurrentHashMap

Java Generics

Java ForkJoinPool

P2P Networks Introduction

















Close TOC
All Tutorial Trails
All Trails
Table of contents (TOC) for this tutorial trail
Trail TOC
Table of contents (TOC) for this tutorial
Page TOC
Previous tutorial in this tutorial trail
Previous
Next tutorial in this tutorial trail
Next