如何仅在截断的单元格上动态设置工具提示

How to set tooltip ONLY on truncated cell dynamically

我的目标是在文本被截断的单元格上显示工具提示,但在运行时也会如此,这意味着如果调整列的大小,一些工具提示可能出现或消失。

在我的细胞工厂中,我以为会有一个 属性 我可以绑定或收听,并且只是 link 一个工具提示,但我发现 none。

这是我在 cell factory 中的每个单元格上设置工具提示的代码:

public void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);

    if (empty || item == null) {
        setText(null);
        setGraphic(null);
    } else {
        setText(adapter.getCellContentParser().toString(item));
        setTooltip(new Tooltip(this.getText());
    }
}

请注意我在网站上寻找所有可能的解决方案,比如这些:

How to show tooltip only on those table cells which text is ellipsized (...)?

当您启动程序时它们非常好,但是当您调整列大小时不起作用

有没有我遗漏的东西?我觉得很奇怪,这种东西没有可观察的属性。

这有点hack,但是你可以在执行布局后通过搜索单元格下方的场景图来检索单元格中的子Text节点,然后为工具提示创建绑定检查单元格的文本是否与文本的文本相同。请注意,我们在这里忽略单元格上的任何图形集,因此这只会搜索显示单元格 text 属性:

的文本节点
setCellFactory(tc -> new TableCell<>() {
    private Text text ;
    private Tooltip tooltip = new Tooltip() ;

    {
        tooltip.textProperty().bind(textProperty());
    }

    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);

        if (item == null || empty) {
            setText("");
        } else {
            setText(item);
        }
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        if (text == null) {
            text = findTextNode(this);
            bindTooltip();
        }               
    }

    private void bindTooltip() {
        tooltipProperty().bind(Bindings.createObjectBinding(() -> {
            if (getItem()==null) return null ;
            if (getText().equals(text.getText())) return null ;
            return tooltip ;
        }, text.textProperty(), itemProperty(), textProperty()));
    }

    // recursively search through child node graph to find the Text node
    private Text findTextNode(Node root) {
        // Ignore the graphic and all its child nodes:
        if (root == getGraphic()) {
            return null ;
        }
        if (root instanceof Text) {
            return (Text) root ;
        }
        if (root instanceof Parent) {
            for (Node node : ((Parent)root).getChildrenUnmodifiable()) {
                Text text = findTextNode(node);
                if (text != null) return text ;
            }
        }
        return null ;
    }
});

这是一个完整的工作示例:

import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * JavaFX App
 */
public class App extends Application {

    @Override
    public void start(Stage stage) {

        var table = new TableView<Item>();
        var itemCol = column("Item", Item::nameProperty);
        var valueCol = column("Value", Item::valueProperty);
        table.getColumns().add(itemCol);
        table.getColumns().add(valueCol);

        itemCol.setCellFactory(tc -> new TableCell<>() {
            private Text text ;
            private Tooltip tooltip = new Tooltip() ;

            {
                tooltip.textProperty().bind(textProperty());
            }

            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);

                if (item == null || empty) {
                    setText("");
                } else {
                    setText(item);
                }
            }

            @Override
            protected void layoutChildren() {
                super.layoutChildren();
                if (text == null) {
                    text = findTextNode(this);
                    bindTooltip();
                }               
            }

            private void bindTooltip() {
                tooltipProperty().bind(Bindings.createObjectBinding(() -> {
                    if (getItem()==null) return null ;
                    if (getText().equals(text.getText())) return null ;
                    return tooltip ;
                }, text.textProperty(), itemProperty(), textProperty()));
            }

            // recursively search through child node graph to find the Text node
            private Text findTextNode(Node root) {
                // Ignore the graphic and all its child nodes:
                if (root == getGraphic()) {
                    return null ;
                }
                if (root instanceof Text) {
                    return (Text) root ;
                }
                if (root instanceof Parent) {
                    for (Node node : ((Parent)root).getChildrenUnmodifiable()) {
                        Text text = findTextNode(node);
                        if (text != null) return text ;
                    }
                }
                return null ;
            }
        });

        var rng = new Random();
        for (int i = 1 ; i <= 40 ; i++) {
            table.getItems().add(new Item("Item "+i, rng.nextInt(100)));
        }

        var scene = new Scene(new BorderPane(table));
        stage.setScene(scene);
        stage.show();
    }

    public static <S,T>  TableColumn<S,T> column(String title, Function<S, Property<T>> prop) {
        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
        return col ;
    }

    public static class Item {
        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty value = new SimpleIntegerProperty();
        public Item(String name, int value) {
            this.name.set(name);
            this.value.set(value);
        }

        public final StringProperty nameProperty() {
            return name ;
        }

        public final IntegerProperty valueProperty() {
            return value ;
        }
    }

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

}