Thursday, February 1, 2018

JavaFX and TableView

The TableView control in JavaFX is a flexible and powerful interface. Every TableView object is parameterized by a data type that represents a row of the table. The documentation encourages developers to create row objects that represent each column using ObservableValue types. Following the documentation, it is necessary to create three methods (a setter and two different getters) for each of these values.

I find this approach exceedingly verbose, and in this blog post I will demonstrate an alternative that is hinted at in the documentation. I will assume that the reader is comfortable creating a JavaFX interface using SceneBuilder. I will just present the controller to which the FXML file can be attached.

This is a simple application that lets the user enter names and ages, which are then displayed in the table. The table is not editable. First, we define a data type for the table rows:

public class NameAgeRow {
  private String name;
  private int age;
 
  public NameAgeRow(String name, int age) {
    this.name = name;
    this.age = age;
  }
 
  public NameAgeRow(String name, String age) {
    this(name, Integer.parseInt(age));
  }
 
  public String getName() {return name;}
  public int getAge() {return age;}
}

Next, we define the Controller for the application:

import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;

public class Controller {
  @FXML
  TableView<NameAgeRow> table;
  @FXML
  TableColumn<NameAgeRow,String> names;
  @FXML
  TableColumn<NameAgeRow,Integer> ages;
 
  @FXML
  TextField name;
  @FXML
  TextField age;
  @FXML
  Button add;
 
  @FXML
  void initialize() {
    add.setOnAction(e -> {
      table.getItems().add(new NameAgeRow(name.getText(), 
                                          age.getText()));
      name.setText("");
      age.setText("");
    });
  
    names.setCellValueFactory(cdf -> 
      new SimpleStringProperty(cdf.getValue().getName()));
    ages.setCellValueFactory(cdf -> 
      new SimpleIntegerProperty(cdf.getValue().getAge()).asObject());
  }
}

The overall structure is straightforward. For each column, we define a lambda that obtains the value from the NameAgeRow object for the current table row. A real convenience of this approach is that it can be made to work with any user-defined class that represents the row data. I find this more convenient and flexible than what is described in the documentation.

No comments:

Post a Comment