Namek Dev
a developer's log
NamekDev

JavaFX: Prevent TextField auto-selection on refocus

May 15, 2016

JavaFX punched me in the face with a feature which I consider as a kind of general over-assumption and as a bug in The Console. When TextField is refocused through other event than click then it auto selects it’s whole content. I want my previous selection or caret position.

A workaround/hack/fix is introduced in this post. Oh, by the way, there’s a bug report.

The Bug

As I noted, JavaFX team doesn’t consider this as a bug. It’s rather a feature which I don’t want to have turned on but don’t have a choice about it.

TextField performs automatic text selection of it’s content in following situations (in my app):

  • hit TAB key on Console Output
  • manual requestFocus() call

The Solution

Algorithm:

  1. When focus is lost, remember selection/caret positions.
  2. When focus regained, simply bring back selection/caret positions.

However! Automatic selection (the bug) occurs after focus event handler is being executed which is why we need to defer state reloading. To make the delay as shortest as possible, simply run the code inside Platform#runLater().

import javafx.application.Platform
import javafx.beans.value.ChangeListener
import javafx.scene.control.IndexRange
import javafx.scene.control.TextField

/**
 * This class is a hack around JavaFX TextField to bring
 * back previous text selection or caret position after
 * refocused by TAB key.
 * <p>By default whole TextField content is selected,
 * this code prevents from it.</p>
 *
 * {@link https://bugs.openjdk.java.net/browse/JDK-8092126}
 * {@link http://stackoverflow.com/questions/14965318/javafx-method-selectall-just-works-by-focus-with-keyboard}
 */
class TextFieldCaretWatcher {
    val TextField field
    var IndexRange oldSelection
    var int oldCaretPos


    new(TextField field) {
        this.field = field
        field.focusedProperty.addListener(focusedListener)
    }

    def void dispose() {
        field.focusedProperty.removeListener(focusedListener)
    }

    private def void bringBackState() {
        if (oldSelection != null && oldSelection.length > 0) {
            field.selectRange(oldSelection.start, oldCaretPos)
        }
        else {
            field.selectRange(oldCaretPos, oldCaretPos)
        }
    }

    val ChangeListener<Boolean> focusedListener = [input, wasFocused, isFocused |
        val justLostFocus = wasFocused && !isFocused
        val justGainedFocus = !wasFocused && isFocused

        if (justLostFocus) {
            oldCaretPos = field.caretPosition
            oldSelection = field.selection
        }
        else if (justGainedFocus) {
            // HACK JavaFX: runLater because text selection occurs after this event
            Platform.runLater [
                bringBackState()
            ]
        }
    ]
}

References

Daj Się Poznać, the-console
comments powered by Disqus