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:
The TableColumn API:
Cell factories support, which allows a customization of the cell content in both states: display and editing
Specification of mindWidth, prefWidth, maxWidth, as well as that of fixed size columns
Resizing by the user, on running
Column reordering by the user, on running
Minimally, we need the following classes in order to create a table:
TableView <S>
, where S
is the type of the object containing the list of items from TableViewTableColumn <S,T>
, where S
is the type of the TableView generic type and T is the type of the content of all the cells of this TableColumn
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:
tableColumn
: the TableColumn
instance behind TableCell
tableView
, the TableView
associated with TableCell
tableRow
, the TableRow
where the TableCell
is placedIn 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:
S
, the generic type of TableView
T
, the content type in all the TableColumn
cells. 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.