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.

Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
Adil
  • 4,503
  • 10
  • 46
  • 63

7 Answers7

64

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 );
          }
        }
    );
  }
}
Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
  • 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. – Martin Andersson Sep 02 '13 at 14:13
  • This no longer seems to work with JavaFX 8. `tf == null` is still true at the moment `initialize` runs. – skiwi Jun 03 '14 at 11:45
  • @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? – Sergey Grinev Jun 03 '14 at 12:00
  • 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? – Pavel_K Sep 14 '17 at 10:47
  • @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. – Sergey Grinev Sep 14 '17 at 14:30
  • @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. – Pavel_K Sep 14 '17 at 14:45
  • @Pavel_K this looks a bit unreliable. I've provided a slightly more convenient method in the answer for the complex cases as yours. – Sergey Grinev Sep 14 '17 at 15:45
  • Thank you very much. Your second solution works and it seems to be the best solution for such problem. – Pavel_K Sep 14 '17 at 16:14
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());
Community
  • 1
  • 1
SedJ601
  • 12,173
  • 3
  • 41
  • 59
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. :)

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
codepleb
  • 10,086
  • 14
  • 69
  • 111
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.

nickthecoder
  • 121
  • 1
  • 3
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.

FableBlaze
  • 1,785
  • 3
  • 16
  • 21
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);
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.

Coemgenus
  • 171
  • 1
  • 4