javajavafxtooltip

How to get displayed tooltip height (not tooltip.getHeight)


I want to show tooltip while clicking one button.

My Application Pic

Relative position between this button and tooltip looks like:

Relative Position

I calculate the coordinates of Point B by:

B.y = (Button.minY + Button.maxY) / 2 - Tooltip.height / 2

I found that the value obtained by tooltip.getHeight() is larger than the actual height of the displayed tooltip.

tooltip.getHeight() seems to be the height of the Window, but what I actually want is the height of the displayed tip.

enter image description here

Following is the simple code, you can use it by calling showTooltip(Button) method:

public void showTooltip(Button btn) {
    btn.setOnAction(event -> {
        Tooltip tooltip = new Tooltip();
        tooltip.setText("TestTooltip");
        tooltip.setAutoHide(true);
        tooltip.setOnShown(windowEvent -> {
            Point2D anchor = anchor(btn, tooltip);
            tooltip.setX(anchor.getX());
            tooltip.setY(anchor.getY());
        });
        tooltip.show(btn.getScene().getWindow());
    });
}

private static Point2D anchor(final Node node, final Tooltip tooltip) {
    final Bounds bounds = node.localToScreen(node.getBoundsInLocal());
    final double tipHeight = tooltip.getHeight();
    final int xOffset = 2;

    double x = bounds.getMaxX() + xOffset;
    double y = (bounds.getMinY() + bounds.getMaxY()) / 2 - tipHeight / 2;
    // Not Correct
    System.out.println(tipHeight);

    return new Point2D(Math.max(x, 0), Math.max(y, 0));
}

Run Code Result


Solution

  • There's two things causing the misalignment:

    1. The default anchor location of a tooltip is CONTENT_TOP_LEFT.

    2. The default application user-agent stylesheet (modena.css) gives tooltips a drop shadow with a non-zero y-offset.

    Neither of those two points are documented, as far as I can tell.

    To properly center the tooltip you need to set the anchor location to WINDOW_TOP_LEFT and you need to account for the y-offset of the drop shadow. Here's a proof-of-concept:

    import java.util.function.Function;
    import javafx.application.Application;
    import javafx.geometry.Bounds;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Tooltip;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Line;
    import javafx.stage.PopupWindow.AnchorLocation;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        var button = new Button("Test");
    
        var tooltip = new Tooltip("Test button's tooltip.");
        tooltip.setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
        tooltip.setOnShown(
            _ -> {
              var bounds = button.localToScreen(button.getBoundsInLocal());
              tooltip.setAnchorX(bounds.getMaxX() + 5);
              // '3' accounts for the y-offset of the tooltip's drop shadow
              tooltip.setAnchorY(bounds.getCenterY() - tooltip.getHeight() / 2 + 3);
            });
        button.setTooltip(tooltip);
    
        // these lines only exist to help see the alignment
        var topLine = createLine(Color.RED, button, Bounds::getMinY);
        var centerLine = createLine(Color.GREEN, button, Bounds::getCenterY);
        var bottomLine = createLine(Color.RED, button, Bounds::getMaxY);
    
        var root = new StackPane(topLine, centerLine, bottomLine, button);
        var scene = new Scene(root, 300, 150);
        primaryStage.setScene(scene);
        primaryStage.show();
      }
    
      private Line createLine(Color stroke, Button button, Function<Bounds, Double> mapper) {
        var line = new Line();
        line.endXProperty().bind(button.sceneProperty().map(Scene::getWidth));
        line.startYProperty().bind(button.boundsInParentProperty().map(mapper));
        line.endYProperty().bind(line.startYProperty());
        line.setStroke(stroke);
        line.setStrokeWidth(2);
        line.setManaged(false);
        return line;
      }
    }
    

    Output:

    Screenshot of running proof-of-concept.

    This code adds 3 to the y-anchor of the tooltip to account for the y-offset of the drop shadow. The value of 3 was taken from modena.css. It may be different across different versions of JavaFX. I tested on JavaFX 24.0.1. A more robust approach would probably be to get the effect from the tooltip. Note it's the label within the tooltip that actually has the drop shadow effect. Of course, there's no guarantee the effect will remain a drop shadow in future versions of JavaFX.

    You could also customize the styling of the tooltip. That way you know exactly what you need to account for. For instance, you could remove the effect entirely.

    Or you could use your own Popup or PopupControl. That might make more sense semantically. Plus, Tooltip comes with functionality you may not need.