JavaFX: Prevent TextField auto-selection on refocus
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:
- When focus is lost, remember selection/caret positions.
- 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()
]
}
]
}