JavaFX TreeTableView失去焦点后选中行文本颜色异常问题
问题分析
你遇到的核心问题是:自定义TreeTableCell时,仅监听了行的选中状态,未结合TreeTableView的焦点状态进行文本颜色控制。默认Modena样式中,选中行的文本在控件有焦点时为白色,失去焦点时变为黑色,但你的自定义逻辑没有处理焦点变化,导致失去焦点后文本颜色未按预期切换。
解决方案
要优雅解决这个问题,需要同时监听两个状态的变化,并在颜色判断中结合这两个状态:
- 监听
TreeTableRow的selectedProperty(行选中状态) - 监听
TreeTableView的focusedProperty(控件焦点状态) - 根据「行是否选中」+「控件是否有焦点」的组合,设置对应的文本颜色:
- 行选中且控件有焦点:文本白色(默认选中样式)
- 行选中但控件无焦点:文本黑色(默认失焦选中样式)
- 行未选中:使用自定义文本颜色
修改后的完整代码
import javafx.application.Application; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableCell; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableRow; import javafx.scene.control.TreeTableView; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.stage.Stage; public class SelectionBarColorFocus extends Application { BorderPane bp = new BorderPane(); @Override public void start(Stage primaryStage) { TreeTableView<Person> treeTableView = new TreeTableView<>(); // 姓名列 TreeTableColumn<Person, String> nameColumn = new TreeTableColumn<>("Name"); nameColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().nameProperty()); nameColumn.setCellFactory(param -> new TreeTableCell<Person, String>() { final ImageView imv = new ImageView(); final Text text = new Text(""); final HBox hbox = new HBox(imv, text); { // 监听行选中状态变化 tableRowProperty().flatMap(TreeTableRow::selectedProperty).addListener((obs, oldVal, newVal) -> updateTextFill()); // 监听TreeTableView焦点状态变化 treeTableViewProperty().flatMap(TreeTableView::focusedProperty).addListener((obs, oldVal, newVal) -> updateTextFill()); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { text.setText(item); setGraphic(hbox); // 移除直接设置颜色的代码,交由updateTextFill处理 } else { text.setText(""); setGraphic(null); } // 初始化时更新一次颜色 updateTextFill(); } private void updateTextFill() { TreeTableRow<Person> row = getTableRow(); if (row != null && row.isSelected()) { // 选中状态:控件有焦点则白,无焦点则黑 text.setFill(getTreeTableView().isFocused() ? Color.WHITE : Color.BLACK); } else { // 未选中状态用自定义颜色 text.setFill(Color.ORANGE); } } }); // 地址列(默认实现,无需自定义) TreeTableColumn<Person, String> addressColumn = new TreeTableColumn<>("Address"); addressColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().addressProperty()); // 年龄列 TreeTableColumn<Person, Number> ageColumn = new TreeTableColumn<>("Age"); ageColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().ageProperty()); ageColumn.setCellFactory(param -> new TreeTableCell<Person, Number>() { final Text text = new Text(""); { // 监听行选中状态变化 tableRowProperty().flatMap(TreeTableRow::selectedProperty).addListener((obs, oldVal, newVal) -> updateTextFill()); // 监听TreeTableView焦点状态变化 treeTableViewProperty().flatMap(TreeTableView::focusedProperty).addListener((obs, oldVal, newVal) -> updateTextFill()); } @Override public void updateItem(Number item, boolean empty) { super.updateItem(item, empty); if (item != null && item.intValue() != -1 && !empty) { text.setText(item.toString()); setGraphic(text); } else { text.setText(""); setGraphic(null); } // 初始化时更新一次颜色 updateTextFill(); } private void updateTextFill() { TreeTableRow<Person> row = getTableRow(); if (row != null && row.isSelected()) { // 选中状态:控件有焦点则白,无焦点则黑 text.setFill(getTreeTableView().isFocused() ? Color.WHITE : Color.BLACK); } else { // 未选中状态用自定义颜色 text.setFill(Color.BLUE); } } }); nameColumn.setPrefWidth(150); addressColumn.setPrefWidth(200); ageColumn.setPrefWidth(100); treeTableView.getColumns().addAll(nameColumn, addressColumn, ageColumn); // 构建数据 TreeItem<Person> rootItem = new TreeItem<>(new Person("Persons", "", -1)); rootItem.getChildren().addAll( new TreeItem<>(new Person("Alice", "456 Park Ave", 25)), new TreeItem<>(new Person("Bob", "789 Broadway", 35)), new TreeItem<>(new Person("Carol", "987 Elm St", 40)), new TreeItem<>(new Person("John", "123 Main St", 40)) ); rootItem.setExpanded(true); treeTableView.setRoot(rootItem); Button b = new Button("Row Lost Focus"); StackPane st = new StackPane(); st.getChildren().add(b); bp.setCenter(treeTableView); bp.setBottom(st); Scene scene = new Scene(bp, 500, 300); primaryStage.setScene(scene); primaryStage.setTitle("JavaFX TreeTableView SelectionBar Lost Focus"); primaryStage.show(); } public static void main(String[] args) { launch(args); } public static class Person { private StringProperty name; private StringProperty address; private IntegerProperty age; public Person(String name, String address, int age) { this.name = new SimpleStringProperty(name); this.address = new SimpleStringProperty(address); this.age = new SimpleIntegerProperty(age); } public StringProperty nameProperty() { return name; } public StringProperty addressProperty() { return address; } public IntegerProperty ageProperty() { return age; } } }
关键改动说明
- 添加焦点状态监听:在自定义单元格的初始化块中,新增对
TreeTableView.focusedProperty()的监听,确保焦点变化时触发颜色更新 - 修正颜色判断逻辑:
updateTextFill方法中,结合行选中状态和TreeTableView焦点状态设置文本颜色,完全对齐默认Modena样式的行为 - 移除硬编码颜色设置:在
updateItem中删除直接设置text.setFill()的代码,避免覆盖监听逻辑的结果 - 初始化时更新颜色:在
updateItem末尾调用updateTextFill(),确保单元格初始化时颜色正确
内容的提问来源于stack exchange,提问作者tretonis
相关产品推荐
相关产品推荐

