如何在 cellFactory 中正确使用监听器
How to properly use a listener in a cellFactory
TL;DR: 侦听器也在其他单元格上激活。
我有一个 TreeView
,其中包含表示自定义 class 的不同 TreeItem
的数据。如果基础数据的 BooleanProperty
发生变化,则单元格应更改其颜色。如果再次发生变化,则应去除颜色。
我使用了监听器,但是当我滚动 TreeView
时,某个单元格的 属性 变化也会改变其他单元格的颜色。当 运行 我的 MWE 并右键单击某些单元格、滚动、再次单击等时,可以重现此行为。只需滚动即可清理 TreeView,以便相关单元格暂时不在视图中。
我可以删除侦听器,但只有当单元格在滚动离开并返回后重新出现时,颜色才会改变。
问题是:如何在 cellFactory 中正确使用监听器?
MWE
CellFactoryQuestion.java
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);
}
}
Styles.css
.tree-cell:custom {
-fx-background-color: salmon;
}
问题是您没有注销侦听器。在调用 super.updateItem
之前执行此操作。这允许您使用 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);
...
TL;DR: 侦听器也在其他单元格上激活。
我有一个 TreeView
,其中包含表示自定义 class 的不同 TreeItem
的数据。如果基础数据的 BooleanProperty
发生变化,则单元格应更改其颜色。如果再次发生变化,则应去除颜色。
我使用了监听器,但是当我滚动 TreeView
时,某个单元格的 属性 变化也会改变其他单元格的颜色。当 运行 我的 MWE 并右键单击某些单元格、滚动、再次单击等时,可以重现此行为。只需滚动即可清理 TreeView,以便相关单元格暂时不在视图中。
我可以删除侦听器,但只有当单元格在滚动离开并返回后重新出现时,颜色才会改变。
问题是:如何在 cellFactory 中正确使用监听器?
MWE
CellFactoryQuestion.java
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);
}
}
Styles.css
.tree-cell:custom {
-fx-background-color: salmon;
}
问题是您没有注销侦听器。在调用 super.updateItem
之前执行此操作。这允许您使用 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);
...