如何将工具提示添加到 JavaFX 的矩形区域 Canvas

How do I add a Tooltip to a rectangular region of a JavaFX Canvas

在我的 JavaFX 应用程序中,我有一个包含多列的 TableView,其中一列以图形形式显示数据。为此,我创建了一个 CanvasCell 对象,该对象创建并管理自己的 Canvas 来处理绘图。绘图部分效果很好。

我现在想将 Tooltips 放在 Canvas/Cell 中的某些区域。每个 Cell 可能有多个 Tooltips(这阻止我在 Cell 级别添加 Tooltip)并且它们应该只在图表的特定区域触发。但是,我根本无法让它发挥作用。我似乎不太了解显示节点层次结构的交互(阅读 "at all"),无法将 Tooltip 放置在它实际工作的任何地方。

JavaFX 的文档很少,Google + SO 对于我尝试过的所有搜索结果都是空白的。有没有人知道如何做这种事情,或者我现在应该把它写成 "not an option"。

有关信息,CanvasCellupdateItem() 上的扩展 Canvas 对象中调用了一个 draw() 函数。我试图创建 Tooltip 的代码位于 draw() 函数内部,看起来像:

    Rectangle rect = new Rectangle(leftVal, topVal, width, height);
    gc.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
    Tooltip tooltip = new Tooltip("Tooltip Text");
    Tooltip.install(rect, tooltip);

但该代码的编写更多是出于希望而不是其他任何东西,并且不会在界面中生成任何有用的东西。

如果有人能指出正确的方向,我将不胜感激。

根据@Slaw 的建议,我有相同的解决方案。我的想法是让它更加集中,这样你就可以通过你的节点和你想要显示工具提示的区域。

在下面的演示中,您可以使用 setToolTips() 作为多个节点的静态实用方法。

注:部分逻辑引用自Tooltip核心实现;)

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.HashMap;
import java.util.Map;

public class MultiTooltipDemo extends Application {
    private double lastMouseX;
    private double lastMouseY;
    private static int TOOLTIP_XOFFSET = 10;
    private static int TOOLTIP_YOFFSET = 7;

    @Override
    public void start(Stage stage) throws Exception {
        StackPane root = new StackPane();
        Scene sc = new Scene(root, 600, 600);
        stage.setScene(sc);
        stage.show();

        StackPane box1 = new StackPane();
        box1.setMaxSize(200, 200);
        box1.setStyle("-fx-background-color:red, blue, yellow, green; -fx-background-insets: 0 100 100 0, 0 0 100 100, 100 100 0 0, 100 0 0 100;");
        root.getChildren().add(box1);

        Map<String, Rectangle2D> tooltips = new HashMap<>();
        tooltips.put("I am red", new Rectangle2D(0, 0, 100, 100));
        tooltips.put("I am blue", new Rectangle2D(100, 0, 100, 100));
        tooltips.put("I am yellow", new Rectangle2D(0, 100, 100, 100));
        tooltips.put("I am green", new Rectangle2D(100, 100, 100, 100));
        setToolTips(box1, tooltips);

    }

    private void setToolTips(Node node, Map<String, Rectangle2D> tooltips) {
        Duration openDelay = Duration.millis(1000);
        Duration hideDelay = Duration.millis(5000);
        Tooltip toolTip = new Tooltip();

        Timeline hideTimer = new Timeline();
        hideTimer.getKeyFrames().add(new KeyFrame(hideDelay));
        hideTimer.setOnFinished(event -> {
            toolTip.hide();
        });

        Timeline activationTimer = new Timeline();
        activationTimer.getKeyFrames().add(new KeyFrame(openDelay));
        activationTimer.setOnFinished(event -> {
            Bounds nodeScreenBounds = node.localToScreen(node.getBoundsInLocal());
            double nMx = nodeScreenBounds.getMinX();
            double nMy = nodeScreenBounds.getMinY();
            toolTip.setText("");
            tooltips.forEach((str, bounds) -> {
                double mnX = nMx + bounds.getMinX();
                double mnY = nMy + bounds.getMinY();
                double mxX = mnX + bounds.getWidth();
                double mxY = mnY + bounds.getHeight();
                if (lastMouseX >= mnX && lastMouseX <= mxX && lastMouseY >= mnY && lastMouseY <= mxY) {
                    toolTip.setText(str);
                }
            });
            if (!toolTip.getText().isEmpty()) {
                toolTip.show(node.getScene().getWindow(), lastMouseX + TOOLTIP_XOFFSET, lastMouseY + TOOLTIP_YOFFSET);
                hideTimer.playFromStart();
            }
        });

        node.setOnMouseMoved(e -> {
            double buffPx = 2;
            double eX = e.getScreenX();
            double eY = e.getScreenY();
            // Not hiding for slight mouse movements while tooltip is showing
            if (hideTimer.getStatus() == Animation.Status.RUNNING) {
                if (lastMouseX - buffPx <= eX && lastMouseX + buffPx >= eX && lastMouseY - buffPx <= eY && lastMouseY + buffPx >= eY) {
                    return;
                }
            }
            lastMouseX = e.getScreenX();
            lastMouseY = e.getScreenY();
            toolTip.hide();
            hideTimer.stop();
            activationTimer.playFromStart();
        });

        node.setOnMouseExited(e -> {
            toolTip.hide();
            activationTimer.stop();
            hideTimer.stop();
        });
    }

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

如果您不需要图示的时序控制, you can simply install the Tooltip on the enclosing Canvas and leverage Shape::contains来调节文本,如下所示。

node.setOnMouseMoved(e -> {
    tooltips.forEach((color, bounds) -> {
        if (bounds.contains(e.getX(), e.getY())) {
            tooltip.setText(color.toString());
        }
    });
});

按照建议 here, Java 9 and later provide control over Tooltip 通过属性 showDelayshowDuration 计时。

针对 Swing 说明了类似的方法。

import javafx.application.Application;
import javafx.scene.shape.Rectangle;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;

import java.util.HashMap;
import java.util.Map;

/**
 * @see 
 * @see 
 */
public class CanvasTooltipDemo extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        StackPane root = new StackPane();
        Scene sc = new Scene(root, 400, 400);
        stage.setScene(sc);
        Canvas canvas = new Canvas(200, 200);
        root.getChildren().add(canvas);

        Map<Color, Rectangle> tooltips = new HashMap<>();
        tooltips.put(Color.RED, new Rectangle(0, 0, 100, 100));
        tooltips.put(Color.BLUE, new Rectangle(100, 0, 100, 100));
        tooltips.put(Color.YELLOW, new Rectangle(0, 100, 100, 100));
        tooltips.put(Color.GREEN, new Rectangle(100, 100, 100, 100));
        GraphicsContext gc = canvas.getGraphicsContext2D();
        tooltips.forEach((color, bounds) -> {
            gc.setFill(color);
            gc.fillRect(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
        });

        setToolTips(canvas, tooltips);
        stage.show();
    }

    private void setToolTips(Node node, Map<Color, Rectangle> tooltips) {
        Tooltip tooltip = new Tooltip();
        Tooltip.install(node, tooltip);
        node.setOnMouseMoved(e -> {
            tooltips.forEach((color, bounds) -> {
                if (bounds.contains(e.getX(), e.getY())) {
                    tooltip.setText(color.toString());
                }
            });
        });
        node.setOnMouseExited(e -> {
            tooltip.hide();
        });
    }

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