textareascreenjavafx-8caret

Caret position relative to screen coordinates in javaFX


I am trying to find a way to get the corresponding screen location of the caret-position in a text area in JavaFX. I need the location to show Popups in text at the caret location.

I found request or it here: https://bugs.openjdk.java.net/browse/JDK-8090849

and some workarounds here: https://community.oracle.com/thread/2534556

They work somehow, but there are a few issues with location not updating correctly sometimes. Does anyone have a suggestion of how to get the caret position in terms of screen X and Y?


Solution

  • Just wanted to follow-up with an answer to this question for TextField controls in JavaFX. I'm sure the same could apply to other text input controls as well. I got the idea from looking at some code that involved changing the default colour of the caret using a subclass of TextFieldSkin class. If you look closely, the TextFieldSkin superclass holds a reference to the Path instance which represents the caret in a protected field called caretPath. Although this is kind of a hack'ish solution, it does provide developers with the absolute coordinates of the Caret in a much safer way than most of the hacks I've seen out there.

    public class TextFieldCaretControlSkin extends TextFieldSkin {
        public TextFieldCaretControlSkin(TextField textField, Stage stage) {
            super(textField);
            Popup popup = new Popup();
    
            // Make the popup appear to the right of the caret
            popup.setAnchorLocation(PopupWindow.AnchorLocation.CONTENT_BOTTOM_LEFT);
            // Make sure its position gets corrected to stay on screen if we go out of screen
            popup.setAutoFix(true);
            // Add list view (mock autocomplete popup)
            popup.getContent().add(new ListView<String>());
    
            // listen for changes in the layout bounds of the caret path
            caretPath.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
                @Override
                public void changed(ObservableValue<? extends Bounds> observable, 
                                    Bounds oldValue, Bounds newValue) {
                    popup.hide();
                    // get the caret's x position relative to the textfield.
                    double x = newValue.getMaxX(); 
                    // get the caret's y position relative to the textfield.
                    double y = newValue.getMaxY(); 
                    Point2D p = caretPath.localToScene(x, y);
    
                    /*
                     * If the coordinates are negatives then the Path is being
                     * redrawn and we should just skip further processing.
                     */
                    if (x == -1.0 || y == -1.0)
                        return;
    
                    // show the popup at these absolute coordinates.                    
                    popup.show(textField,
                            p.getX() + caretPath.getScene().getX() + 
                                 caretPath.getScene().getWindow().getX(),
                            p.getY() + caretPath.getScene().getY() + 
                                 caretPath.getScene().getWindow().getY() - 
                                 newValue.getHeight()); // put the popup on top of the caret
                }
            });
        }
    }
    

    To use you'd have to embed this in some sort of subclassed text input control and remember to do textField.setSkin(new TextFieldCaretControlSkin(textField)). There may be better ways to do this since I am not a JavaFX expert but I just wanted to share this solution with the rest of the world just in case it provided some insight.

    Hope this helps!