JavaFX 如何在鼠标悬停到列时突出显示 TableColumn 而不是 TableRow

JavaFX how to highlight TableColumn instead of TableRow when mouse is hovered to column

我试图找到一个解决方案来突出显示 table 列,当鼠标位于该列顶部时,但我一直无法找到解决方案。有人已经找到解决方案了吗?我已经尝试查看 css 和 ScenicView 的组件,但它并没有太大帮助。

我想这只有在很多 "hacks" 的情况下才有可能。

TableColumn只存在于TableView的数据模型中,基于模型渲染的view部分是按行渲染的,不是按列渲染的。所以每个 TableCell 都有一个 table 行作为父级 - 而不是 table 列。

如果以相反的方式实现,table 会呈现在列中,我们可以轻松实现这一点。


现在,如果您真的想这样做,则必须执行以下操作:

  1. 编写自定义 TableCell 并将其注册为所有列的 CellRenderer。
  2. 如果您的自定义单元格被(未)悬停,检索其列并通知同一列中的所有其他单元格。

但是: 如果 table 变大和/或应用程序所在的计算机 运行 变慢,这将减慢您的应用程序。

如果您使用的是 Java 8,则可以使用 CSS 伪 class 来很好地完成此操作。 CSS pseudoclasses 的性能非常好(据说,至少,它是更改 JavaFX 控件样式的最快方法),并且由于 table 视图只创建少量 table 个单元格,即使对于非常大的数据集,这也应该表现很好。

定义一个外部 css class 来设置 table 单元格的样式,并按如下方式应用自定义伪class:

.table-cell:column-hover {
    -fx-background-color: -fx-cell-focus-inner-border, -fx-selection-bar  ;
    -fx-background-insets: 0, 0 0 1 0 ;
    -fx-background: -fx-selection-bar ;
}

(这里的 -fx-background-color 实际上改变了背景颜色:-fx-background 只是一个确保文本填充相对于背景保持 suitable 颜色的技巧。)

现在,为每一列定义一个布尔值 属性。为每一列创建一个单元格工厂,当鼠标移到单元格上时将布尔值 属性 设置为 true,并在鼠标移开时将其设置为 false。让每个单元格观察布尔值 属性 并在单元格更改时设置伪 class 状态。

这是一个示例,使用 standard tutorial 中常用的 Person table。有趣的代码在 createCol(...) 方法中:

import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableViewTest extends Application {

    private static final PseudoClass COLUMN_HOVER_PSEUDO_CLASS = PseudoClass.getPseudoClass("column-hover");

    @Override
    public void start(Stage primaryStage) {
        TableView<Person> table = new TableView<>();
        TableColumn<Person, String> firstNameCol = createCol("First Name", Person::firstNameProperty, 150);
        TableColumn<Person, String> lastNameCol = createCol("Last Name", Person::lastNameProperty, 150);
        TableColumn<Person, String> emailCol = createCol("Email", Person::emailProperty, 200);

        table.getItems().addAll(
            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")
        );

        table.getColumns().add(firstNameCol);
        table.getColumns().add(lastNameCol);
        table.getColumns().add(emailCol);

        VBox root = new VBox(15, table);
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 800, 600);

        scene.getStylesheets().add("table-column-hover.css");

        primaryStage.setScene(scene);
        primaryStage.show();
    }


    private TableColumn<Person, String> createCol(String title, 
            Function<Person, ObservableValue<String>> mapper, double size) {

        TableColumn<Person, String> col = new TableColumn<>(title);

        col.setCellValueFactory(cellData -> mapper.apply(cellData.getValue()));

        // Is the column being hovered over with the mouse?
        BooleanProperty columnHover = new SimpleBooleanProperty();

        col.setCellFactory(column -> {

            // basic table cell:
            TableCell<Person, String> cell = new TableCell<Person, String>() ;
            cell.textProperty().bind(cell.itemProperty());

            // when the mouse hovers over the cell, set the columnHover to indicate 
            // the mouse is over the column:
            cell.hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
                columnHover.set(isNowHovered);
            });

            // update the column-hover pseudoclass state for this cell when the column is hovered over
            // note this will activate the pseudoclass when the mouse is over any cell in this column
            columnHover.addListener((obs, columnWasHovered, columnIsNowHovered) -> 
                cell.pseudoClassStateChanged(COLUMN_HOVER_PSEUDO_CLASS, columnIsNowHovered)
            );

            return cell ;
        });

        col.setPrefWidth(size);

        return col ;
    }

    public class Person {
        private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
        private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
        private final StringProperty email = new SimpleStringProperty(this, "email");

        public Person(String firstName, String lastName, String email) {
            this.firstName.set(firstName);
            this.lastName.set(lastName);
            this.email.set(email);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final java.lang.String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final java.lang.String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final java.lang.String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final java.lang.String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final StringProperty emailProperty() {
            return this.email;
        }

        public final java.lang.String getEmail() {
            return this.emailProperty().get();
        }

        public final void setEmail(final java.lang.String email) {
            this.emailProperty().set(email);
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}