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:
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 explicitCallback
, 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.
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
CSS Changes on MVC App Not Working
Can't Use the Same Animation in Reverse for Class Toggle
How to Hide a <Select> Arrow in Firefox 30+
A:Visited Links - Opacity Not Working
Layout Using Vh Does Not Scale with Zoom
Removing Page Scrollbars in IE8 (Overflow:Hidden Not Working)
Set Background-Image to a Blob: Uri
How to Make a Button Stretch Across the Width of a Column
Stop PHPstorm File Watchers Running Recursively (With Autoprefixer)
Are Margin and Padding Most Disbalanced Thing Among All Browser
Sass: Set Variable at Compile Time
How to Select Every Other Group of Three in CSS
Why Doesn't CSS-Calc() Work When Using 0 Inside the Equation
Consistently Sizing a <Textarea> Under Ie, Ff, Safari/Chrome
Ie Z-Index Trouble on Element with Transparent Background
Css3 Transition ( Vendor Prefixes) Crashes Safari Immediately