Search code examples
javafxjavafx-8

JavaFX-8 Make text caret visible in readonly textarea


JavaFX textfields do not show a text caret if you set them to readonly mode. Here is an example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;

public class TextAreaReadOnly extends Application {

    public TextAreaReadOnly() {
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        TextArea textarea = new TextArea();
        textarea.setText("This is all\nreadonly text\nin here.");
        textarea.setEditable(false);
        Scene scene = new Scene(textarea, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }    

    public static void main(String[] args) {
        launch(args);
    }
}

While it is still possible to select text with Shift+Cursor keys, no caret is displayed. Does anyone know a workaround for this?


Solution

  • Triggered by Neil's answer, I tried a quick test of my suggestion to extend TextAreaSkin and replace the caretVisible property by one that doesn't check for editability. Seems to work (not thoroughly tested, though) - but requires reflective access of super's private blink property. Obviously dirty and not possible in security restricted contexts ...

    public static class MyTextAreaSkin extends TextAreaSkin {
    
        public MyTextAreaSkin(TextArea textInput) {
            super(textInput);
            caretVisible = new BooleanBinding() {
                { bind(textInput.focusedProperty(), textInput.anchorProperty(), 
                        textInput.caretPositionProperty(),
                        textInput.disabledProperty(), displayCaret , blinkProperty() );}
                @Override protected boolean computeValue() {
                    return !blinkProperty().get() &&  displayCaret.get() && textInput.isFocused() &&
                            (isWindows() || (textInput.getCaretPosition() == textInput.getAnchor())) 
                            && !textInput.isDisabled(); 
                }
            };
            // rebind opacity to replaced caretVisible property
            caretPath.opacityProperty().bind(new DoubleBinding() {
                { bind(caretVisible); }
                @Override protected double computeValue() {
                    return caretVisible.get() ? 1.0 : 0.0;
                }
            });
    
        }
    
        BooleanProperty blinkAlias;
    
        BooleanProperty blinkProperty() {
            if (blinkAlias == null) {
                Class<?> clazz = TextInputControlSkin.class;
                try {
                    Field field = clazz.getDeclaredField("blink");
                    field.setAccessible(true);
                    blinkAlias = (BooleanProperty) field.get(this);
                } catch (NoSuchFieldException | SecurityException 
                       | IllegalArgumentException | IllegalAccessException e) {
                    // TBD: errorhandling
                    e.printStackTrace();
                }
    
            }
            return blinkAlias;
        }
    
    }
    
    // usage in a custom TextArea
    TextArea textarea = new TextArea() {
    
        @Override
        protected Skin<?> createDefaultSkin() {
            return new MyTextAreaSkin(this);
        }
    
    };