TL;DR: Listener is also activated on other cells.
I have a TreeView
containing different TreeItem
s representing data of a custom class. If a BooleanProperty
of the underlying data changes, the cell should change its colour. If it changes again, the colour should be removed.
I use a listener, but when I scroll through the TreeView
, a changing property of a certain cell also changes the colour of other cells. This behaviour can be reproduced when running my MWE and right clicking on some cells, scrolling, clicking again, and so on. The TreeView might be cleaned by just scrolling so that the concerned cells are out a view for a moment.
I could remove the listener, but then the colour will only be changed, if the cell reappears after scrolling away and back to it.
The question is: How can I use a listener properly in a cellFactory?
package cellfactoryquestion;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CellFactoryQuestion extends Application {
/** Custom class used as underlying data of TreeItems */
class CustomObject {
String label;
BooleanProperty state = new SimpleBooleanProperty(false);
CustomObject(String s) { label = s; }
}
/** Cell Factory for CustomObject */
class CustomTreeCell extends TreeCell<CustomObject>{
PseudoClass customClass = PseudoClass.getPseudoClass("custom");
@Override
protected void updateItem(CustomObject co, boolean empty) {
super.updateItem(co, empty);
if (empty || co == null) {
setText(null);
setGraphic(null);
pseudoClassStateChanged(customClass, false);
} else {
setText(co.label);
setGraphic(null);
// BEGIN PROBLEMATIC
/* define background color of cell according to state */
pseudoClassStateChanged(customClass, co.state.getValue());
co.state.addListener((o, ov, nv) -> {
pseudoClassStateChanged(customClass, nv);
});
// END PROBLEMATIC
/* if right click, switch state */
this.setOnContextMenuRequested(e -> {
co.state.setValue(co.state.getValue() ^ true);
});
}
}
}
@Override
public void start(Stage primaryStage) {
/* define TreeView 1/3 */
TreeView tw = new TreeView();
TreeItem rootTreeItem = new TreeItem(new CustomObject("Root"));
rootTreeItem.setExpanded(true);
/* define TreeView 2/3 */
for (int c = 0; c != 5; c++) {
TreeItem ci = new TreeItem(new CustomObject("Cat " + c));
rootTreeItem.getChildren().add(ci);
ci.setExpanded(true);
for (int i = 0; i != 5; i++) {
TreeItem ii = new TreeItem(new CustomObject("Item " + i));
ci.getChildren().add(ii);
}
}
/* define TreeView 3/3 */
tw.setRoot(rootTreeItem);
tw.setCellFactory(value -> new CustomTreeCell());
/* define Scene */
StackPane root = new StackPane();
root.getChildren().add(tw);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add("/styles/Styles.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
.tree-cell:custom {
-fx-background-color: salmon;
}
The problem is that you do not unregister the listener. Do this before calling super.updateItem
. This allows you to retrieve the old item using getItem
:
class CustomTreeCell extends TreeCell<CustomObject>{
private final ChangeListener<Boolean> listener = (o, ov, nv) -> pseudoClassStateChanged(customClass, nv);
PseudoClass customClass = PseudoClass.getPseudoClass("custom");
@Override
protected void updateItem(CustomObject co, boolean empty) {
// remove listener from old item
CustomObject oldItem = getItem();
if (oldItem != null) {
oldItem.state.removeListener(listener);
}
super.updateItem(co, empty);
if (empty || co == null) {
setText(null);
setGraphic(null);
pseudoClassStateChanged(customClass, false);
} else {
setText(co.label);
setGraphic(null);
/* define background color of cell according to state */
pseudoClassStateChanged(customClass, co.state.getValue());
co.state.addListener(listener);
...