Note: this is about a very specific bug and not a duplicate of Programmatically change the TableView row appearance, but it is quite related to that question.
I have a table with a CheckBox and when the user select that row (i.e click on the CheckBox) I change the style of that row to make it clear that one is selected.
But, when the user apply a filter to change the displayed data on the TableView, I cannot style each row depending on the select property. And when I debugged, I saw that it was actually going where it should set the style, but no style change at all.
Below is the minimal code that I could make to reproduce this error:
DataView.java
package table;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class DataView extends Application{
TableView<DataRow> table = new TableView<DataRow>();
ComboBox<Integer> cb = new ComboBox<Integer>();
ObservableList<DataRow> masterData = FXCollections.observableArrayList();
ObservableList<DataRow> filteredData = FXCollections.observableArrayList();
@Override
public void start(Stage stage) throws Exception {
VBox root = new VBox();
initializeGUI();
addDummyData();
applyFilters();
root.getChildren().addAll(cb,table);
Scene scene = new Scene(root);
// scene.getStylesheets().add("/res/styleSummary.css");
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private void applyFilters() {
filteredData = FXCollections.observableArrayList(new Callback<DataRow, Observable[]>() {
@Override
public Observable[] call(DataRow tr) {
return new Observable[] { tr.selectedProperty() };
}
});
filteredData.addListener(new ListChangeListener<DataRow>() {
@Override
public void onChanged(javafx.collections.ListChangeListener.Change<? extends DataRow> change) {
try{
while (change.next()) {
if (change.wasUpdated()) {
int i = 0;
for (Node n: table.lookupAll("TableRow")){
if(i==change.getFrom()){
@SuppressWarnings("unchecked")
TableRow<DataRow> row = (TableRow<DataRow>) n;
if (table.getItems().get(i).getSelected())
row.getStyleClass().add("selected");
else
row.getStyleClass().remove("selected");
}
i++;
}
updateMaster(filteredData.get(change.getFrom()));
}
}
}catch(Exception e){
}
}
private void updateMaster(table.DataRow changed) {
for (DataRow tr : masterData) {
if (tr.getNumSlices()==changed.getNumSlices()) {
tr.selectedProperty().set(changed.getSelected());
break;
}
}
}
});
filteredData.addAll(masterData);
for(DataRow dr: masterData){
if(dr.getNumSlices()==cb.getValue()){
filteredData.remove(dr);
}
}
table.setItems(filteredData);
//This fails:
int i = 0;
for (Node n: table.lookupAll("TableRow")){
TableRow<DataRow> row = (TableRow<DataRow>) n;
if (table.getItems().get(i).getSelected())
row.getStyleClass().add("selected");
else
row.getStyleClass().remove("selected");
i++;
if (i == table.getItems().size())
break;
}
}
@SuppressWarnings("unchecked")
public void initializeGUI(){
//combobox
cb.getItems().addAll(null,2,4,5);
cb.valueProperty().addListener(new ChangeListener<Number>(){
@Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
applyFilters();
}
});
//table
TableColumn<DataRow, Integer> numColumn = new TableColumn<DataRow, Integer>();
numColumn.setCellValueFactory(new PropertyValueFactory<DataRow, Integer>("numSlices"));
TableColumn<DataRow, Boolean> selectionColumn = new TableColumn<DataRow, Boolean>();
selectionColumn.setCellValueFactory(new PropertyValueFactory<DataRow, Boolean>("selected"));
final Callback<TableColumn<DataRow, Boolean>, TableCell<DataRow, Boolean>> cellFactory = CheckBoxTableCell
.forTableColumn(selectionColumn);
selectionColumn.setCellFactory(new Callback<TableColumn<DataRow, Boolean>, TableCell<DataRow, Boolean>>() {
@Override
public TableCell<DataRow, Boolean> call(TableColumn<DataRow, Boolean> column) {
TableCell<DataRow, Boolean> cell = cellFactory.call(column);
cell.setAlignment(Pos.CENTER);
return cell;
}
});
selectionColumn.setCellFactory(cellFactory);
selectionColumn.setEditable(true);
table.getColumns().addAll(numColumn,selectionColumn);
table.setEditable(true);
}
public void addDummyData(){
for(int i=0;i<13;i++)
masterData.add(new DataRow(i,false));
}
public static void main(String[] args) throws Exception { launch(args); }
}
The, the DataRow which is essentially a Integer and Boolean type:
DataRow.java
package table;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
public class DataRow {
private Integer numSlices;
private BooleanProperty selected;
public DataRow(int numSlices, boolean selected) {
this.selected = new SimpleBooleanProperty(selected);
this.numSlices = numSlices;
}
public Integer getNumSlices() {return numSlices;}
public void setNumSlices(int num){this.numSlices = num;}
public void setSelected(boolean value) {selected.set(value);}
public boolean getSelected() {return selected.get();}
public BooleanProperty selectedProperty(){return selected;}
}
style.css
.selected {
-fx-font-weight: bold;
-fx-font-size: 13;
}
And just to make it more specific, the error is probably here :
//(DataView.java) -- line 89
//This fails:
int i = 0;
for (Node n: table.lookupAll("TableRow")){
TableRow<DataRow> row = (TableRow<DataRow>) n;
if (table.getItems().get(i).getSelected())
row.getStyleClass().add("selected");
else
row.getStyleClass().clear();
i++;
if (i == table.getItems().size())
break;
}