Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Changing JavaFX table view row styling with non-visible property using row level property binding.
import javafx.beans.property.SimpleBooleanProperty;
/**
* Simple abstract class that has active boolean property
*
* @author jaakkju
*/
abstract public class AbstractToggleTableItem {
private final SimpleBooleanProperty active = new SimpleBooleanProperty(true);
public AbstractToggleTableItem(boolean active) {
this.active.set(active);
}
public SimpleBooleanProperty activeProperty() {
return active;
}
public void setActive(boolean active) {
this.active.set(active);
}
public boolean isActive() {
return active.get();
}
}
@CHARSET "UTF-8";
.table-row-cell:disabled {
-fx-background-color: #c1c1c1;
-fx-control-inner-background: #c1c1c1;
}
import java.util.Arrays;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
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.text.Font;
import javafx.stage.Stage;
public class TableViewRowStyleSample extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> rows =
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) {
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn<Person, String> tcFirstName = new TableColumn<>("First Name");
tcFirstName.setMinWidth(100);
tcFirstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> tcLastName = new TableColumn<>("Last Name");
tcLastName.setMinWidth(100);
tcLastName.setCellValueFactory(new PropertyValueFactory<>("lastName"));
TableColumn<Person, String> tcEmail = new TableColumn<>("Email");
tcEmail.setMinWidth(200);
tcEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
table.setItems(rows);
table.getColumns().addAll(Arrays.asList(tcFirstName, tcLastName, tcEmail));
table.setRowFactory(p -> new ToggleTableRow<>());
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
final Button btnDisable = new Button("Disable random");
btnDisable.setOnAction((ActionEvent event) -> {
/* Randomly select one row and change it's active value */
Random generator = new Random();
int i = generator.nextInt(rows.size());
Person item = rows.get(i);
if(item.isActive()) {
item.setActive(false);
} else {
item.setActive(true);
}
});
final HBox buttons = new HBox(5);
buttons.setAlignment(Pos.CENTER);
buttons.getChildren().addAll(btnDisable);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, buttons);
Scene scene = new Scene(vbox, 450, 500);
stage.setTitle("JavaFX table row style update with non-visible property");
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static class Person extends AbstractToggleTableItem {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty email;
private Person(String fName, String lName, String email) {
super(true);
this.firstName = new SimpleStringProperty(this, "firstName", fName);
this.lastName = new SimpleStringProperty(this, "lastName", lName);
this.email = new SimpleStringProperty(this, "email", email);
}
public final String getFirstName() {
return firstName.get();
}
public final void setFirstName(String fName) {
firstName.set(fName);
}
public StringProperty firstNameProperty() {
return firstName ;
}
public final String getLastName() {
return lastName.get();
}
public final void setLastName(String fName) {
lastName.set(fName);
}
public final StringProperty lastNameProperty() {
return lastName ;
}
public final String getEmail() {
return email.get();
}
public final void setEmail(String fName) {
email.set(fName);
}
public final StringProperty emailProperty() {
return email ;
}
}
}
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableRow;
/**
* Item change makes binding between table item and table row, so that we can force
* UpdateItem call when active property changes. We don't
* have to force the update it's skin with visibility true/false
* trick. ex.
*
* ((TableColumn) getTableView().getColumns().get(0)).setVisible(false);
* ((TableColumn) getTableView().getColumns().get(0)).setVisible(true);
*
* Binding created is a weak reference so garbage collection should work properly.
*
* @author jaakkju
* @param <T>
*/
public class ToggleTableRow<T extends AbstractToggleTableItem> extends TableRow<T> {
private final SimpleBooleanProperty active = new SimpleBooleanProperty();
/* Current Item which is bound to this table row */
private T currentItem = null;
public ToggleTableRow() {
super();
active.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
/* If item is the same we know that the update came
from actual property change and not from the row reuse */
if (currentItem != null && currentItem == getItem()) {
updateItem(getItem(), isEmpty());
}
});
/*
JavaFX reuses rows in the same way as it reuses table cells,
item behind the row changes ex. if row if scrolled so that it is not visible.
*/
itemProperty().addListener((ObservableValue<? extends T> observable, T oldValue, T newValue) -> {
/* When the item changes, we unbind the
old property and start listening to the new */
active.unbind();
if (newValue != null) {
active.bind(newValue.activeProperty());
/* We change current item only after
binding since since it trickers change in the properties */
currentItem = newValue;
}
});
}
@Override
final protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
/* Setting disabled sets row's pseudoclass to disabled, we can use that
value to assign inactive styling to the row. */
setDisable(item != null && !item.isActive());
setEditable(item != null && !item.isActive());
}
}
@mirikog

This comment has been minimized.

Copy link

@mirikog mirikog commented Sep 3, 2018

Thank you very much, this works perfectly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.