RequestFocus in TextField doesn't work
Asked Answered
D

7

18

I use JavaFX 2.1 and I created GUI using FXML, in the controller of this GUI I added myTextField.requestFocus();.

But I always get the focus in the other control.

Deuced answered 5/10, 2012 at 10:42 Comment(0)
A
63

At the time of initialize() controls are not yet ready to handle focus.

You can try next trick:

@Override
public void initialize(URL url, ResourceBundle rb) {
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            tf.requestFocus();
        }
    });
}

For tricky complex applications (like Pavel_K has in the comments) you may want to repeat this routine several times and call method line next one:

private void requestFocusOrDieTrying(Node node) {
    Platform.runLater(() -> {
        if (!node.isFocused()) {
            node.requestFocus();
            requestFocusOrDieTrying(node);
        }
    });
}

Note this is the undocumented approach and it may be wise to add a limit for repetitions to avoid endless loop if something changed or broke in future Java releases. Better to lose focus than a whole app. :)


Example with the described threshold:

@Override
public void requestFocus() {
  requestFocus( getNode(), 3 );
}

private void requestFocus( final Node node, final int max ) {
  if( max > 0 ) {
    runLater(
        () -> {
          if( !node.isFocused() ) {
            node.requestFocus();
            requestFocus( node, max - 1 );
          }
        }
    );
  }
}
Arlina answered 5/10, 2012 at 10:57 Comment(8)
I had a somewhat related problem, my TextArea refused to get the focus programmatically even though the Stage was visible and active and the current running thread was the JavaFX Application thread. But if I followed the example and queued a new Runnable it worked. Really strange, I have no words for it.Proliferation
This no longer seems to work with JavaFX 8. tf == null is still true at the moment initialize runs.Uncut
@skiwi, it means there is an error in FXML: check @FXML annotation, "fx:id" tag and spelling of the variable in both. All @FXML-ed controls should be not null at initialize() time. Otherwise what a point in having initialize() method at all?Arlina
This solution works about 10% cases and 90% cases doesn't work. I mean the same code, the same program. Is there any other way how to detect when text field is ready to request focus?Bromeosin
@Pavel_K works for me 100% times. If you have a specific case it would be great to show it in a separate question. Another option can be codepleb's answer -- it should work without any timing tricks.Arlina
@Sergey Grinev Thank you for your answer. The problem is that program is on platform, there are many node one above another (StackPanes) and its architecture is not so simple, that's why it is difficult to take this problem out of the code. What helped us is Platform.runLater(() -> { Platform.runLater(() -> {Platform.runLater(() -> {textInput.requestFocus(); });});}); Although it seems to work with two Platform.runLater we decided to put one more for sure. It seems that there are some timings issues in JavaFX with this focus, that's why one Platform.runLater works 10% cases.Bromeosin
@Pavel_K this looks a bit unreliable. I've provided a slightly more convenient method in the answer for the complex cases as yours.Arlina
Thank you very much. Your second solution works and it seems to be the best solution for such problem.Bromeosin
G
12

The exact same answer as @Sergey Grinev. Make sure your version of java is up-to-date (JDK 1.8 or later).

Platform.runLater(()->myTextField.requestFocus());
Gesso answered 11/8, 2016 at 15:24 Comment(0)
I
10

If you requestFocus(); after initializing the scene, it will work!

Like this:

Stage stage = new Stage();
GridPane grid = new GridPane();
//... add buttons&stuff to pane

Scene scene = new Scene(grid, 800, 600);

TEXTFIELD.requestFocus();

stage.setScene(scene);
stage.show();

I hope this helps. :)

Issykkul answered 25/8, 2013 at 8:31 Comment(0)
A
4

This can occur when the Scene property for the Node is not yet set. Alas, the scene property can take a "long" time to be set.

The child node's scene property lags when a scene is first created, and also, when items are added to some parents, such as a TabPane (oddly some parents seem immune, I'm not sure why).

The correct strategy, which has always worked for me :

if (myNode.scene) == null {
    // listen for the changes to the node's scene property,
    // and request focus when it is set
} else {
    myNode.requestFocus()
}

I have a handy Kotlin extension function which does this.

fun Node.requestFocusOnSceneAvailable() {
    if (scene == null) {
        val listener = object : ChangeListener<Scene> {
            override fun changed(observable: ObservableValue<out Scene>?, oldValue: Scene?, newValue: Scene?) {
                if (newValue != null) {
                    sceneProperty().removeListener(this)
                    requestFocus()
                }
            }
        }
        sceneProperty().addListener(listener)
    } else {
        requestFocus()
    }
}

You can then call it from within you code like so :

myNode.requestFocusOnSceneAvailable()

Perhaps somebody would like to translate it to Java.

Alkyne answered 17/9, 2018 at 17:48 Comment(0)
C
1

I ran into the same problem using JavaFX 11 and solved it in a similar way that nickthecoder proposed.

ChangeListener<Scene> sceneListener = new ChangeListener<Scene>() {
    @Override
    public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
        if (newValue != null) {
            editInput.requestFocus();
            editInput.sceneProperty().removeListener(this);
        }
    }
};
editInput.sceneProperty().addListener(sceneListener);

Basicly just add a listener to the sceneProperty of the node and in that listener request focus once the scene is set. I also wrote it in such a way that the listener will be removed after it is invoked.

Choice answered 10/6, 2020 at 11:41 Comment(0)
K
1

I would rather using timer to enforce focus to text field. The process of checking whether or not the text field has focus, is done in a separate (background) thread. While the process of requesting focus is done in the GUI thread, with the help of Platform.runLater().

//I'd rather using timer to enforce focus
Timer checkIfTFIsFocusedTimer = new Timer();
TimerTask checkIfTFIsFocusedTask = new TimerTask() {
    @Override
    public void run() {
        if (!textField.isFocused()) {
            Platform.runLater(() -> {                        
                textField.requestFocus();
            });
        } else {
            checkIfTFIsFocusedTimer.cancel();
        }
    }
};
checkIfTFIsFocusedTimer
        .scheduleAtFixedRate(checkIfTFIsFocusedTask,
        0, 100);
Keil answered 13/7, 2021 at 9:23 Comment(0)
L
0

The older answers account for the case of Platform.runLater not working, but this answer covers also the case of multiple requests on multiple nodes.

Problem is: the order in which the scene property becomes non-null for the nodes, and thence the order in which the added listeners get called, is not necessarily the same as the order in which the two listeners were added. And so this order:

requestFocusOnSceneAvailable(node1)
requestFocusOnSceneAvailable(node2)

might unexpectedly result in this order:

node2.requestFocus()
node1.requestFocus()

A solution requires having the listeners call requestFocus() only on the most recent node, which can be tracked with a static variable:

private static Node nodeToRequestFocusOnOnceSceneAvailable;

public static void requestFocusOnceSceneAvailable(Node node) {
    
    // Remember this node as the latest node requested to receive focus.
    nodeToRequestFocusOnOnceSceneAvailable = node;
    
    // Schedule the focus request to happen whenever
    // JavaFX finally adds the node to the scene.
    Listeners.addAndFire(node.sceneProperty(), new ChangeListener<Scene>() {
        @Override
        public void changed(ObservableValue<? extends Scene> observable, Scene oldScene, Scene newScene) {
            if (newScene != null) {
                if (node == nodeToRequestFocusOnOnceSceneAvailable) {
                    node.requestFocus();
                    
                    // We no longer need to remember this node,
                    // since its focus has been requested.
                    nodeToRequestFocusOnOnceSceneAvailable = null;
                }
                
                // We no longer need the listener
                // after it has run once.
                observable.removeListener(this);
            }
        }
    });
}

Note, this solution assumes there is only one scene.

Lunik answered 21/7, 2020 at 2:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.