Search code examples
javafxtooltipjavafx-8

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


In my JavaFX app I have a TableView with multiple columns, one of which displays data in a graphical form. To do this I have created a CanvasCell object that creates and manages its own Canvas to deal with the drawing. The drawing part works just fine.

I'd now like to put Tooltips over some regions within the Canvas/Cell. There may be multiple Tooltips per Cell (which prevents me from adding the Tooltip at the Cell level) and they should only trigger in specific regions of the graph. However, I'm not managing to get it functioning at all. I don't seem to understand the interactions of Display Node hierarchy well enough (read "at all") to be able to place the Tooltip anywhere where it will actually work.

Documentation for JavaFX is sparse and Google + SO has come up blank for all searches that I've tried. Is there anyone who knows how to do this sort of thing or should I just write it off as "not an option" for now.

For info, the CanvasCell calls a draw() function inside an extended Canvas object on updateItem(). The code in which I've tried to create a Tooltip sits inside that draw() function and looks like:

    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);

but that code was written more in hope than anything else and doesn't generate anything useful in the interface.

If someone can point me in the right direction, I will be very grateful.


Solution

  • If you don't need the timing control illustrated here, you can simply install the Tooltip on the enclosing Canvas and leverage Shape::contains to condition the text as shown below.

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

    As suggested here, Java 9 and later provide control over Tooltip timing via the properties showDelay and showDuration.

    A similar approach is illustrated here for Swing.

    image

    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 https://stackoverflow.com/a/53785468/230513
     * @see https://stackoverflow.com/a/53753537/230513
     */
    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);
        }
    }