EDITING BOARD
RO
EN
×
▼ BROWSE ISSUES ▼
Issue 29

Java FX Visual Components

Silviu Dumitrescu
Line manager@Telenav



Diana Bălan
Map analyst@Telenav
PROGRAMMING

Tables represent one of the most powerful tools used in JavaFX for displaying data, supporting the following actions:

Several classes of JavaFX SDK API are used for data representation in a tabular form. Among these, the most important are: TableView, TableColumn and TableCell. We can populate a table from a data model and then we can apply a cell factory to it.

The facilities of TableView include:

Table creation

Minimally, we need the following classes in order to create a table:

TableView is built from a number of TableColumn instances. Each TableColumn is responsible for displaying and editing the content of a column. In addition, TableColumn contains properties for:

When we create a TableColumn instance, probably the most important properties to set are: the text (that is, what we display in the header of the column) and the cell value factory (used for populating individual cells of the column).

The TableCell class represents the intersection of a line with a column in TableView. It contains the following properties:

In order to identify the intersection, TableCell contains an index property.

Cell is used for an individual cell in a TableView. Each cell is associated to a single data item, represented by the item property. A cell is responsible with rendering the item residing in it, which is usually a text. A cell allows customization through a cell factory.

The Cell API is used for the virtualization of controls such as ListView, TreeView and TableView. A cell is a labelled control, used for rendering a unit in one of the above mentioned controls. Cell is responsible both for the display as well as for the editing of the item. Besides the text, Cell can be represented by other controls such as CheckBox, ChoiceBox or any Node such as HBox, GridPane or even Rectangle. Since ListView, TreeView, TableView, as well as other such controls can be used for displaying a large amount of data, it is not practical to create a Cell for each item of the control. Each cell is reused; this is what this virtualized control does.

Since Cell is a Control, it has a model behind. Its Skin is responsible for defining the look and layout, while the Behaviour is responsible for the manipulation of events and using this contained information to modify the state. Cell is edited through CSS, like any other control.

In order to specialize a cell used for a TableView we should provide an implementation of the callback cellFactory() function defined on TableView. Cell factory is called by the platform any time a new cell has to be created. The implementation of a cell factory is responsible for the creation of a Cell instance and for the configuration necessary for that Cell to react to the changes of its state.

Cell factory is responsible for the virtualization of container skins to render the predefined representation of a Cell item. For example, in a ListView it converts the items to a String and calls Labeled.setText (String). If we wish to specialize the cell used in ListView, we need to provide the implementation of the callback function defined on ListView.

Cell factory is called by the platform any time it determines that a cell should be created. For instance, a ListView has 10 million items. The creation of all those 10 million is very costly. Thus, the implementation of the ListView skin will create only as many cells as to fill up the visual space. If the visual space is resized, the system will decide whether it is necessary to create other cells. In this case, it will call cellFactory() (if there is one) in order to create a Cell implementation. If none is provided, then the predefined implementation is used.

ObservableList is the data model underlying TableView. A TableView instance is defined as:

TableView table = new TableView<>();

Thus, we have defined a primary table. The data model is created based on an ObservableList. We can set it directly in the TableView. For example:

ObservableList teamMembers = getTeamMembers();

table.setItems(teamMembers);

Once the TableView item list is set, it is automatically updated any time the teamMembers list is modified. If the item list is available before TableView is instanced, it is possible for us to send it directly in the constructor.

What we also need to do is to divide the data contained in the model into one or several TableColumn instances. In order to create a two column TableView displaying firstName and lastName, we will use the following code:

public class DataTable extends Application {
    TableView table = new TableView<>();
    BorderPane root = new BorderPane();
    VBox mainBox = new VBox(); // Container for content
    final ObservableList teamMembers = FXCollections
            .observableArrayList(new Person("Ion", "Tech"), new Person("Petre","Petrescu"), new Person("Doru", "Dorescu"), new Person("Vasile", "Vasilescu"));

    @Override
    public void init() {
        Label centerLbl = new Label("Persons");
        centerLbl.setStyle("-fx-font-size:16pt; -fx-font-weight:bold;");
        table.setItems(teamMembers);
        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol    .setCellValueFactory(new PropertyValueFactory("firstName"));
        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
        table.getColumns().setAll(firstNameCol, lastNameCol);
        mainBox.getChildren().add(centerLbl);
        mainBox.getChildren().add(table);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Data Table");
        root.setCenter(mainBox);
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }

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

By this, we have completely defined the minimal properties required in order to be able to create a TableView instance. No other properties of the Person class will be displayed, since there are no other defined TableColumns. The implementation of a cell factory is responsible not only with the creation of the Cell instance, but also with the configuration of that cell. In the following example, we have created a Callback class whose attributes are instances of the TextAlignment and Format classes, with the parameters:

public class FormattedTableCellFactory implements
    Callback, TableCell> {
    private TextAlignment alignment;
    private Format format;
    public TextAlignment getAlignment() {
        return alignment;
    }

    public void setAlignment(TextAlignment alignment) {
        this.alignment = alignment;
    }

    public Format getFormat() {
        return format;
    }

    public void setFormat(Format format) {
        this.format = format;
    }

    @Override
    public TableCell call(TableColumn p) {
        TableCell cell = new TableCell() {
            @Override
            public void updateItem(Object item, boolean empty) {
                if (item == getItem())
                    return;

                super.updateItem(item, empty);
                if (item == null) {
                    super.setText(null);
                    super.setGraphic(null);
                } else if (format != null) {
                    super.setText(format.format(item));
                } else if (item instanceof Node) {

                    super.setText(null);
                    super.setGraphic((Node) item);
                } else {
                    super.setText(item.toString());
                    super.setGraphic(null);
                }
            }
        };

        cell.setTextAlignment(alignment);
        switch (alignment) {
        case CENTER:
            cell.setAlignment(Pos.CENTER);
            break;

        case RIGHT:
            cell.setAlignment(Pos.CENTER_RIGHT);
            break;

        default:
            cell.setAlignment(Pos.CENTER_LEFT);
            break;
        }

        return cell;
    }
}

Respectively:

FormattedTableCellFactory xx = new FormattedTableCellFactory<>();

xx.setAlignment(TextAlignment.CENTER);
firstNameCol.setCellFactory(xx);

The benefices of using CSSs in order to set the style of a table consist in time efficiency and memory efficiency, the easiness of using and building libraries for the cells, the easiness of customization of display formatting.

We are using CSSs to set the colours of the cell:

.table-cell {
 -fx-padding: 3 3 3 3;
 -fx-background-color: white; 
}
.table-cell:selected { 
-fx-background-color: blue; 
}

In order for table-cell:selected to work, we have to set the cellSelectionEnabled to true.

Many cell implementations extend the IndexedCell instead of the Cell. This allows the adding of other two pseudo-classes: odd and even. With these we can get alternating colouring of the lines by means of a CSS such as:

.table-row-cell:odd{
    -fx-background-color:lightblue;
}

Thank you for reading our article and we are looking forward, as usual, to discuss it further with you.

VIDEO: ISSUE 97 LAUNCH EVENT

Sponsors

  • comply advantage
  • ntt data
  • Betfair
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • Cognizant Softvision
  • BoatyardX
  • Colors in projects