How to Add Tableview Footer in Javafx Tableview

JavaFX .How to make a line of summation(total row) in the bottom of the table?

In JavaFX it's like in Swing: The quickest way to do it is to create a 2nd table.

You can take a look at a summary table sample code on this gist.

Screenshot:

Sample Image

Adding a CheckBox column to an existing TableView

Summary: As noted here, this is likely a bug; steps to avoid the pitfall include these:

  • Verify that the data model exports properties correctly, as shown here.
  • Critically examine the value of replacing PropertyValueFactory with an explicit Callback, when possible, as outlined here, here, here, here and here.

The problem is that CheckBoxTableCell can't find or bind the ObservableProperty<Boolean> based on the parameter supplied:

active.setCellFactory(CheckBoxTableCell.forTableColumn(active));

The CheckBoxTableCell defers to the table column for access to the target Boolean property. To see the effect, replace the active parameter with a Callback that returns the ObservableValue<Boolean> for row i explicitly:

active.setCellFactory(CheckBoxTableCell.forTableColumn(
(Integer i) -> data.get(i).active));

While this makes the checkboxes work, the underlying problem is that the Person class needs an accessor for the active property. Using JavaFX Properties and Binding discusses the property method naming conventions, and the Person class of the Ensemble8 tablecellfactory illustrates a working model class with a property getter for each attribute, also shown below.

With this change PropertyValueFactory can find the newly added BooleanProperty, and the original form of forTableColumn() works. Note that the convenience of PropertyValueFactory comes with some limitations. In particular, the factory's fall-through support for the previously missing property accessor goes unnoticed. Fortunately, the same accessor allows substitution of a simple Callback for each column's value factory. As shown here, instead of PropertyValueFactory,

active.setCellValueFactory(new PropertyValueFactory<>("active"));

Pass a lamda expression that returns the corresponding property:

active.setCellValueFactory(cd -> cd.getValue().activeProperty());

Note also that Person can now be private. Moreover, the use of explicit type parameters affords stronger type checking during compilation.

image

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

/**
* https://stackoverflow.com/a/68969223/230513
*/
public class TableViewSample extends Application {

private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data
= FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com")
);

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

@Override
public void start(Stage stage) {
stage.setTitle("Table View Sample");
stage.setWidth(600);
stage.setHeight(400);

final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));

table.setEditable(true);

TableColumn<Person, Boolean> active = new TableColumn<>("Active");
active.setCellValueFactory(cd -> cd.getValue().activeProperty());
active.setCellFactory(CheckBoxTableCell.forTableColumn(active));

TableColumn<Person, String> firstName = new TableColumn<>("First Name");
firstName.setCellValueFactory(cd -> cd.getValue().firstNameProperty());

TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
lastName.setCellValueFactory(cd -> cd.getValue().lastNameProperty());

TableColumn<Person, String> email = new TableColumn<>("Email");
email.setCellValueFactory(cd -> cd.getValue().emailProperty());

table.setItems(data);
table.getColumns().addAll(active, firstName, lastName, email);

final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(8));
vbox.getChildren().addAll(label, table);

stage.setScene(new Scene(vbox));
stage.show();
}

private static class Person {

private final BooleanProperty active;
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty email;

private Person(String fName, String lName, String email) {
this.active = new SimpleBooleanProperty(true);
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}

public BooleanProperty activeProperty() {
return active;
}

public StringProperty firstNameProperty() {
return firstName;
}

public StringProperty lastNameProperty() {
return lastName;
}

public StringProperty emailProperty() {
return email;
}
}
}

JavaFX: 'disabling' TableView rows and columns

As @Jacks has pointed out, there doesn't seem to be a short-cut for this. So here is how I've solved the problem.

First, we hide the table header and allow cells to be selected individually. The updateTable() method is called every n seconds; it simulates a ScheduledService API call. The MyRow object is just a wrapper for an ObservableList<TableCell>, and we use that because we don't know how many columns there will be until run-time.

COLUMN_HEADER, ROW_FOOTER, etc. are enums.

private void updateTable() {
Pane header = (Pane) myTable.lookup("TableHeaderRow");
header.setVisible(false);
myTable.getSelectionModel().setCellSelectionEnabled(true);
myTable.setLayoutY(-header.getHeight());
myTable.autosize();

List<MyRow> list = new CopyOnWriteArrayList();
Integer maxRows = 5, maxColumns = 5; // For example
MyRow row;
for (int rowId = 0; rowId <= maxRows; rowId++) {
Boolean isEmpty = myTable.getColumns().isEmpty();
row = new MyRow();
Integer total = 0;
for (int colId = 0; colId <= maxColumns; colId++) {
if (isEmpty) {
myTable.getColumns().add(createReactiveColumn());
}
TableCell cell = new MyTableCell();
if (rowId == 0 && colId == 0) {
// Top-left cell
} else if (rowId == 0) {
// Column title
cell.setUserData(COLUMN_HEADER);
cell.setText("CH" + Integer.toString(colId));
} else if (colId == 0) {
// Row title
cell.setUserData(ROW_HEADER);
cell.setText("RH" + Integer.toString(rowId));
} else if (colId == maxColumns) {
// Row 'footer'
cell.setUserData(ROW_FOOTER);
cell.setText(Integer.toString(total));
} else if (rowId == maxRows) {
// Column 'footer'
cell.setUserData(COLUMN_FOOTER);
cell.setText("CF" + Integer.toString(rowId));
} else {
// Data
Integer r = new Random().nextInt(9); // Simulate API data
if (!this.disabledColumns.contains(myTable.getColumns().get(colId))) {
total += r; // Sum of each enabled cell
}
cell.setUserData(DATA);
cell.setText(Integer.toString(r));
}
row.add(cell);
}
list.add(row);
}
myTable.getItems().setAll(list);
}

The TableColumn just employs the setCellFactory() method:

private TableColumn<MyRow, String> createReactiveColumn() {

TableColumn<MyRow, String> column = new TableColumn<MyRow, String>();
column.setCellFactory(new Callback<TableColumn<MyRow, String>, TableCell<MyRow, String>>() {
@Override
public TableCell<MyRow, String> call(TableColumn<MyRow, String> param) {
return new MyTableCell();
}
});
column.setSortable(FALSE);
column.setMinWidth(40d);

return column;
}

MyTableCell adds a MOUSE_CLICKED Event Handler, as well as copying the text from the updated TableCell.

private class MyTableCell extends TableCell<MyRow, String> {

Boolean hasEventHandler = FALSE;

@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty && getTableRow() != null && getTableRow().getItem() != null) {
MyRow er = (MyRow) getTableRow().getItem();
TableCell cell = er.get(getTableView().getColumns().indexOf(getTableColumn()));
this.setText(cell.getText());
if (cell.getUserData() instanceof MyTableCellEnum) {
MyTableCellEnumcellType = (MyTableCellEnum) cell.getUserData();
if (null != cellType && !hasEventHandler) {
switch (cellType) {
case COLUMN_HEADER:
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
toggleColumn(getTableColumn());
}
});
hasEventHandler = TRUE;
break;
case ROW_HEADER:
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
toggleRow(getTableRow());
}
});
hasEventHandler = TRUE;
break;
case DATA:
addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
// Do other action on selection
}
});
break;
default:
break;
}
}
}
}
}
}

Finally, the toggle methods just swap the row / column between a Set of disabled TableRow / TableColumn objects and update the CSS:

private void toggleRow(TableRow tableRow) {
if (this.disabledRows.contains(tableRow)) {
this.disabledRows.remove(tableRow);
tableRow.getStyleClass().remove("cell-disabled");
} else {
this.disabledRows.add(tableRow);
tableRow.getStyleClass().add("cell-disabled");
}
}

private void toggleColumn(TableColumn tableColumn) {
System.out.println(tableColumn);
if (this.disabledColumns.contains(tableColumn)) {
this.disabledColumns.remove(tableColumn);
tableColumn.getStyleClass().remove("cell-disabled");
} else {
this.disabledColumns.add(tableColumn);
tableColumn.getStyleClass().add("cell-disabled");
}
}

It works and I'm mostly happy with it. The inclusion of the hasEventHandler Boolean is not ideal: it seems a bit hacky but I couldn't find any other way of registering an Event Handler only once while ensuring that it actually works.

Comments and suggestions for improvement are welcome. I'll leave it a few days before accepting my own answer in case of a better idea.

How to split a TableView (JavaFX11) in n parts for saving as png

You can try something similar to the code below. The code prints the TableView with ten items. It then clears the TableView and adds ten more items to print. It will continue this process until all of the items are printed.

It opens the print dialog. This will allow you to save the print as a PDF to view it instead of wasting paper.

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class FxTableViewExample1 extends Application
{

private TableView<Book> table;
private ObservableList<Book> data;
private Text actionStatus;

public static void main(String[] args)
{

Application.launch(args);
}

@Override
public void start(Stage primaryStage)
{

primaryStage.setTitle("Table View Example 1");

// Books label
Label label = new Label("Books");
label.setTextFill(Color.DARKBLUE);
label.setFont(Font.font("Calibri", FontWeight.BOLD, 36));
HBox hb = new HBox();
hb.setAlignment(Pos.CENTER);
hb.getChildren().add(label);

// Table view, data, columns and properties
table = new TableView();
data = getInitialTableData();
table.setItems(data);

TableColumn titleCol = new TableColumn("Title");
titleCol.setCellValueFactory(new PropertyValueFactory("title"));
TableColumn authorCol = new TableColumn("Author");
authorCol.setCellValueFactory(new PropertyValueFactory("author"));

table.getColumns().setAll(titleCol, authorCol);
table.setPrefWidth(450);
table.setPrefHeight(300);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

// Status message text
actionStatus = new Text();
actionStatus.setFill(Color.FIREBRICK);

Button button = new Button("Print");

// Vbox
VBox vbox = new VBox(20);
vbox.setPadding(new Insets(25, 25, 25, 25));;
vbox.getChildren().addAll(hb, table, actionStatus, button);

// Scene
Scene scene = new Scene(vbox, 500, 475); // w x h
primaryStage.setScene(scene);
primaryStage.show();

// Select the first row
table.getSelectionModel().select(0);
Book book = table.getSelectionModel().getSelectedItem();
actionStatus.setText(book.toString());

List<List<Book>> bookLists = partition(data, 10);

button.setOnAction((event) -> {
PrinterJob printerJob = PrinterJob.createPrinterJob();
printerJob.showPrintDialog(primaryStage);
for (int i = 0; i < bookLists.size(); i++) {
data.clear();
data.addAll(bookLists.get(i));
printerJob.printPage(table);
}

printerJob.endJob();

});

}

private ObservableList getInitialTableData()
{
List list = new ArrayList();
list.add(new Book("The Thief", "Fuminori Nakamura"));
list.add(new Book("Of Human Bondage", "Somerset Maugham"));
list.add(new Book("The Bluest Eye", "Toni Morrison"));
list.add(new Book("I Am Ok You Are Ok", "Thomas Harris"));
list.add(new Book("Magnificent Obsession", "Lloyd C Douglas"));
list.add(new Book("100 Years of Solitude", "Gabriel Garcia Marquez"));
list.add(new Book("What the Dog Saw", "Malcolm Gladwell"));
list.add(new Book("The Fakir", "Ruzbeh Bharucha"));
list.add(new Book("The Hobbit", "J.R.R. Tolkien"));
list.add(new Book("Strange Life of Ivan Osokin", "P.D. Ouspensky"));
list.add(new Book("The Hunt for Red October", "Tom Clancy"));
list.add(new Book("Coma", "Robin Cook"));

list.add(new Book("A Catskill Eagle", "xxx"));
list.add(new Book("The Children of Men", "xxx"));
list.add(new Book("Clouds of Witness", "xxx"));
list.add(new Book("A Confederacy of Dunces", "xxx"));
list.add(new Book("Consider Phlebas", "xxx"));
list.add(new Book("Consider the Lilies", "xxx"));
list.add(new Book("Cover Her Face", "xxx"));
list.add(new Book("The Cricket on the Hearth", "xxx"));
list.add(new Book("The Curious Incident of the Dog in the Night-Time", "xxx"));
list.add(new Book("The Daffodil Sky", "xxx"));
list.add(new Book("Dance Dance Dance", "xxx"));
list.add(new Book("A Darkling Plain", "xxx"));

list.add(new Book("The Thief", "Fuminori Nakamura"));
list.add(new Book("Of Human Bondage", "Somerset Maugham"));
list.add(new Book("The Bluest Eye", "Toni Morrison"));
list.add(new Book("I Am Ok You Are Ok", "Thomas Harris"));
list.add(new Book("Magnificent Obsession", "Lloyd C Douglas"));
list.add(new Book("100 Years of Solitude", "Gabriel Garcia Marquez"));
list.add(new Book("What the Dog Saw", "Malcolm Gladwell"));
list.add(new Book("The Fakir", "Ruzbeh Bharucha"));
list.add(new Book("The Hobbit", "J.R.R. Tolkien"));
list.add(new Book("Strange Life of Ivan Osokin", "P.D. Ouspensky"));
list.add(new Book("The Hunt for Red October", "Tom Clancy"));
list.add(new Book("Coma", "Robin Cook"));

list.add(new Book("A Catskill Eagle", "xxx"));
list.add(new Book("The Children of Men", "xxx"));
list.add(new Book("Clouds of Witness", "xxx"));
list.add(new Book("A Confederacy of Dunces", "xxx"));
list.add(new Book("Consider Phlebas", "xxx"));
list.add(new Book("Consider the Lilies", "xxx"));
list.add(new Book("Cover Her Face", "xxx"));
list.add(new Book("The Cricket on the Hearth", "xxx"));
list.add(new Book("The Curious Incident of the Dog in the Night-Time", "xxx"));
list.add(new Book("The Daffodil Sky", "xxx"));
list.add(new Book("Dance Dance Dance", "xxx"));
list.add(new Book("A Darkling Plain", "xxx"));

return FXCollections.observableList(list);
}

private static <T> List<List<T>> partition(Collection<T> members, int maxSize)
{
List<List<T>> res = new ArrayList<>();

List<T> internal = new ArrayList<>();

for (T member : members) {
internal.add(member);

if (internal.size() == maxSize) {
res.add(internal);
internal = new ArrayList<>();
}
}
if (internal.isEmpty() == false) {
res.add(internal);
}
return res;
}
}

Work with Value from PropertyValueFactory in TableView - JavaFx

You should use the cellValueFactory to determine which data are displayed in the cells: in this case the data returned by your PropertyValueFactory is the actual double value returned from powerPerAreaProperty().get(), which is exactly what you want.

If you want to control how the data are displayed, you should use a cellFactory. So to display the data in a particular format, including limiting the number of decimal places, you can do:

powerColumn.setCellFactory(tc -> new TableCell<Area, Double>() {
@Override
protected void updateItem(Double power, boolean empty) {
super.updateItem(power, empty);
if (empty) {
setText(null);
} else {
setText(String.format("%.0f", power.doubleValue()));
}
}
});

The point here is that you should not modify the data based on how you want to display it; the purpose of having both cellValueFactory and cellFactory is to separate the display of the data from the actual data itself.

TableView exclude bottom row (total) from sorting

With JavaFX 8 it is possible to define a sorting policy, and the problem is easier to fix. Assuming the row containing the totals is TOTAL:

table.sortPolicyProperty().set(t -> {
Comparator<Row> comparator = (r1, r2) ->
r1 == TOTAL ? 1 //TOTAL at the bottom
: r2 == TOTAL ? -1 //TOTAL at the bottom
: t.getComparator() == null ? 0 //no column sorted: don't change order
: t.getComparator().compare(r1, r2); //columns are sorted: sort accordingly
FXCollections.sort(t.getItems(), comparator);
return true;
});


Related Topics



Leave a reply



Submit