Simple JavaFX TableView Example

JavaFX TableViews are perfect for making spreadsheets or just displaying data. In JavaFX, TableViews are a little trickier than normal UI elements, because you are dealing with data, and data can come and go as you use your application. This tutorial goes over setting up your first JavaFX TableView and populating it with custom data.

javafx_tableview_example
Typical JavaFX TableView in use.

This example uses MVP/MVC design, so it can easily be expanded upon to create a full blown application to use your JavaFX TableView. If you haven’t used SceneBuilder, MVP, Controllers, or FXML to create your JavaFX applications, then you should check out some of my previous tutorials. I recommend starting with the JavaFX Hello World Example and the JavaFX tutorials list.

First create a standard JavaFX project in Eclipse. After creating the JavaFX project, create an FXML file called TableView.fxml. Also create a TableController class. You will recognize these steps from the other JavaFX tutorials on this site.

Open the FXML file with SceneBuilder. Go through standard layout of your scene like resizing your AnchorPane or other Container to your liking. This is all described in the Hello World Example so I won’t go over it again here.

Add a TableView to your AnchorPane or what ever type of Pane you are using for your root element in SceneBuilder. The JavaFX TableView comes with two TableColumns. I deleted them and then added three new columns and renamed them “First Name”, “Last Name”, and “Phone Number”.

That’s almost it for SceneBuilder. You do need to add fx:id’s to your three TableColumns, and set the fully qualified name of your controller. Do that just as in the Hello World Example mentioned above.

Make sure you save your FXML file and refresh it in Eclipse so that it has your JavaFX TableView and the TableColumns. The FXML I ended up with is as follows.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane 
prefHeight="600.0" 
prefWidth="400.0" 
xmlns:fx="http://javafx.com/fxml/1" 
xmlns="http://javafx.com/javafx/8.0.40" 
fx:controller="application.TableController">
   <children>
      <TableView 
      fx:id="customerTable" 
      editable="true" 
      layoutX="14.0" 
      layoutY="14.0" 
      prefHeight="573.0" 
      prefWidth="373.0">
         <columns>
            <TableColumn 
            fx:id="firstNameColumn" 
            prefWidth="104.0" 
            text="First Name" />
            <TableColumn 
            fx:id="lastNameColumn" 
            prefWidth="121.0" 
            text="Last Name" />
            <TableColumn 
            fx:id="phoneNumberColumn" 
            prefWidth="147.0" 
            text="Phone Number" />
         </columns>
      </TableView>
   </children>
</AnchorPane>

Next generate your Controller skeleton inside SceneBuilder and copy it over to your

/**
 * Sample Skeleton for 'TableView.fxml' Controller Class
 */

package application;

import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;

public class TableController {

    @FXML // fx:id="customerTable"
    private TableView<?> customerTable;

    @FXML // fx:id="firstNameColumn"
    private TableColumn<?, ?> firstNameColumn;

    @FXML // fx:id="lastNameColumn"
    private TableColumn<?, ?> lastNameColumn;

    @FXML // fx:id="phoneNumberColumn"
    private TableColumn<?, ?> phoneNumberColumn;

}

Update your Main class just like in the Hello World example. Here’s the code for quick reference.

package application;
	
import java.io.IOException;
import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;


public class Main extends Application {
  
  @Override
  public void start(Stage primaryStage) throws IOException {
    URL url = getClass().getResource("TableView.fxml");
    AnchorPane pane = FXMLLoader.load( url );
    Scene scene = new Scene( pane );
    
    primaryStage.setScene( scene );
    primaryStage.setTitle( "JavaFX TableView" );
    primaryStage.show();
  }
  
	public static void main(String[] args) {
		launch(args);
	}
}

Now all we need is data, a POJO (Plain Old Java Object) to hold it, and the code that tells the JavaFX TableView how to use the data.

Let’s start with our POJO. The POJO holds the data for one row of data to display in the JavaFX TableView. Internally, it will save save the data in SimpleStringProperty objects. SimpleStringProperty objects are from the javafx.beans.property package. Here’s the code.

package application;

import javafx.beans.property.SimpleStringProperty;

public class Customer {
  private final SimpleStringProperty firstName;
  private final SimpleStringProperty lastName;
  private final SimpleStringProperty phone;
  
  
  public Customer(
      String firstName,
      String lastName,
      String phone
      ) {
    this.firstName = new SimpleStringProperty( firstName );
    this.lastName = new SimpleStringProperty( lastName );
    this.phone = new SimpleStringProperty( phone );
  }
  
  
  public String getFirstName() {
    return firstName.get();
  }
  
  public void setFirstName(String firstName) {
    this.firstName.set( firstName );
  }
  
  
  
  public String getLastName() {
    return lastName.get();
  }
  
  public void setLastName(String lastName) {
    this.lastName.set( lastName );
  }
  
  
  
  public String getPhone() {
    return phone.get();
  }
  public void setPhone(String phone) {
    this.phone.set( phone );
  }
}

As you can see, there really isn’t anything tricky about the POJO, other than using the property objects from the javafx.beans.property package.

Now, let’s create the data source. This data could easily come from a database, a restful service, or anywhere really. I’ve hardcoded the data for our TableView to use just for simplicity. I’ve placed the data in a class conveniently called DataSource.

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class DataSource {
  
  private final ObservableList<Customer> data = 
      FXCollections.observableArrayList();
  
  public ObservableList<Customer> getData() {
    return data;
  }

  public DataSource() {
    data.add(new Customer("Bob", "Smith", "800.123.4567"));
    data.add(new Customer("Joe", "Glow", "800.123.4568"));
    data.add(new Customer("Jenny", "Ihop", "800.123.4569"));
  }
}

Note the ObservableList that is created from the FXCollections.observableArrayList() method. We need that special observable list for the TableView and TableColumns to have access to our data.

Okay, now to hook everything up with the Controller. Modify your skeleton code in your TableView’s controller to read as follows.

package application;

import javafx.collections.ObservableList;

/**
 * Sample Skeleton for 'TableView.fxml' Controller Class
 */

import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class TableController {

    @FXML // fx:id="customerTable"
    private TableView<Customer> customerTable;

    @FXML // fx:id="firstNameColumn"
    private TableColumn<Customer, String> firstNameColumn;

    @FXML // fx:id="lastNameColumn"
    private TableColumn<Customer, String> lastNameColumn;

    @FXML // fx:id="phoneNumberColumn"
    private TableColumn<Customer, String> phoneNumberColumn;

    @FXML //basically works like an onload() method
    protected void initialize() {
      
      //setting up the columns
      PropertyValueFactory<Customer, String> firstNameProperty = 
          new PropertyValueFactory<Customer, String>("firstName");
      
      PropertyValueFactory<Customer, String> lastNameProperty = 
          new PropertyValueFactory<Customer, String>("lastName");
      
      PropertyValueFactory<Customer, String> phoneNumberProperty = 
          new PropertyValueFactory<Customer, String>("phone");
      
      firstNameColumn.setCellValueFactory( firstNameProperty );
      lastNameColumn.setCellValueFactory( lastNameProperty );
      phoneNumberColumn.setCellValueFactory( phoneNumberProperty );
      
      //setting up the table data source
      DataSource data = new DataSource();
      ObservableList<Customer> tableItems = data.getData();
      customerTable.setItems( tableItems );
    }
}

A little bit of explaining is in order here.

The @FXML annotations allow the FXML UI elements to be injected into the controller class. That way the controller class has access to the JavaFX TableView and the three JavaFX TableColumn objects.

The @FXML before the initialize() method tells JavaFX to call the method after the FXML is loaded. We use the initialize() method to associate each column with the POJO element it should get it’s data from. That’s what all that PropertyValueFactory and setCellValueFactory() code is about. Next we set the data source of the TableView as being the ObservableList we created in the DataSource class.

That’s really all there is to JavaFX TableView creation and data population. Keep checking back for additional JavaFX tutorials. I’m sure more than one will be about TableViews.