I want to show tooltip while clicking one button.
Relative position between this button and tooltip looks like:
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.
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));
}
There's two things causing the misalignment:
The default anchor location of a tooltip is CONTENT_TOP_LEFT
.
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:
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.