javafxtreetableview

Javafx Scene get the Node when using a tooltip


I have some code which generates a QR code based on a TreeTableRow in a TreeTableView. I have attached a tooltip on the treetable using the code below. The tooltip works well, hoevering the mouse over the Row will display the "Hello World" QR code. However, the image needs to be generated dynamically.

img is generated as below using io.nayuki.qrcodegen, may them live forever.



QrCode qr0 = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM);
BufferedImage img = toImage(qr0, 2, 1, 0xFFFFFF,0x000000); 


Tooltip tp = new Tooltip();
Tooltip.install(treeTableView, tp);
tp.setOnShowing(ev -> {// called just prior to being shown    Point mouse = java.awt.MouseInfo.getPointerInfo().getLocation();
    Point2D local = treeTableView.screenToLocal(mouse.x, mouse.y);
    ImageView iv1 = new ImageView();
    iv1.setImage(SwingFXUtils.toFXImage(img, null));
    tp.setGraphic(iv1);
});

Not much beyong googling / binging the phrase "Javafx find the node under the mouse".

I have since found a workaround by using a row Factory and store the current row in a global variable hoverRow.

row.setOnMouseEntered(event->{
    StockBinsTree.this.hoverRow=row;
});
row.setOnMouseExited(event->{
    StockBinsTree.this.hoverRow=null;
});

The modify the tooltip as follows:

tp.setOnShowing(ev -> {// called just prior to being shown    if(hoverRow!=null) {
       StockBin sbItem = hoverRow.getItem();
       QrCode qr0 = QrCode.encodeText(sbItem.toLink(), QrCode.Ecc.HIGH);
       BufferedImage img = toImage(qr0, 2, 2, 0xFFFFFF,0x000000);  // See QrCodeGeneratorDemo       ImageView iv1 = new ImageView();
       iv1.setImage(SwingFXUtils.toFXImage(img, null));
       tp.setGraphic(iv1);
    }
});

Effectively solved, but the original question on how to find the Node under the mouse is still a mystery for other time.


Solution

  • Assuming your question is:

    how to find the Node under the mouse

    You can determine the currently picked node from mouse event targets, or the pick result intersected nodes.

    scene.addEventFilter(
            MouseEvent.MOUSE_MOVED, e ->
                    System.out.println(
                            "Picking: " + e.getTarget()
                    )
    );
    

    Example

    In the example, the text will change to indicate the node under the current mouse location.

    An event filter monitors mouse movements in the scene and records the currently picked (or targeted) node in a property. A subscription to the property will update the text in the scene and log a history of picked nodes to the console.

    As the mouse listener is set for mouse movements in the scene, if the mouse leaves the scene, the currently picked node will remain set to the last picked node.

    picking

    WheresTheMouseAtApp.java

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.text.Font;
    import javafx.scene.text.Text;
    import javafx.stage.Stage;
    
    public class WheresTheMouseAtApp extends Application {
        private Text text;
    
        @Override
        public void start(Stage stage) throws Exception {
            Scene scene = createScene();
            stage.setScene(scene);
            stage.show();
    
            PickRecorder pickRecorder = new PickRecorder(scene);
            pickRecorder.currentPickedNodeProperty().subscribe(node -> {
                System.out.println("target " + node);
                String simpleNodeText =
                        node != null
                                ? node.getClass().getSimpleName()
                                : " nothing";
    
                text.setText("Picking " + simpleNodeText);
            });
        }
    
        private Scene createScene() {
            Rectangle square = new Rectangle(50, 50, 100, 100);
            square.setFill(Color.BLUE);
    
            Circle circle = new Circle(300,  100, 60);
            circle.setFill(Color.ORANGE);
    
            text = new Text(85, 225, null);
            text.setFont(Font.font(30));
    
            Pane pane = new Pane(square, circle, text);
            pane.setBackground(Background.fill(Color.AZURE));
    
            return new Scene(pane, 400, 300);
        }
    }
    

    PickRecorder.java

    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.input.PickResult;
    import javafx.scene.input.MouseEvent;
    
    class PickRecorder {
        private final ObjectProperty<Node> currentPickedNode = 
                new SimpleObjectProperty<>();
    
        public PickRecorder(Scene scene) {
            scene.addEventFilter(
                MouseEvent.MOUSE_MOVED, e ->
                    currentPickedNode.set(
                        // using either the picked node or target node will work.
                        getPickedNode(e)
                        // getTargetNode(e)
                    )
            );
        }
    
        private static Node getPickedNode(MouseEvent e) {
            PickResult pickResult = e.getPickResult();
    
            return
                    pickResult != null
                            ? pickResult.getIntersectedNode()
                            : null;
        }
    
        private static Node getTargetNode(MouseEvent e) {
            return e.getTarget() instanceof Node targetNode
                    ? targetNode
                    : null;
        }
    
        public ObjectProperty<Node> currentPickedNodeProperty() {
            return currentPickedNode;
        }
    }