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.

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