sesmajster
sesmajster

Reputation: 762

Detect focus out of specific Pane in javafx

How can I detect that the focus has left the specific pane? Because the pane (StackPane) is not focusable, I'm having trouble doing this. For example, I have a StackPane with TextFields and if I press tab several times, it leaves the focused pane and goes to the other pane. I want to prevent this kind of behavior by setting the focus back to the first TextField in the pane if you leave the focus of StackPane.

This is my attempt:

myStackPane().focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
            if (! isNowFocused) {
                setFocus(node);
            }
        });

But I think that this does not work due to StackPane not being focusable. I also tried with isFocused() method like this:

myStackPane.focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
            if(pane.isFocused()){
                System.out.println("not focused");
                setFocus(node);
            }else{
                System.out.println("is focused");
            }
        });

But, as expected, it did not yield the wanted results.

Upvotes: 3

Views: 1518

Answers (1)

Slaw
Slaw

Reputation: 45766

As you've discovered, when a child is focused the parent's focused property is not set to true, so checking if the parent is focused won't help you. You'll have to test if the newly focused Node is a descendant of the parent you wish to keep the focus within; if it's not, request focus on the appropriate child. Here's a rudimentary example:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class App extends Application {

    private static boolean isParentAncestorOfNode(Parent parent, Node node) {
        if (parent == null || node == null || parent == node || node.getParent() == null) {
            return false;
        }

        Parent current = node.getParent();
        do {
            if (current.equals(parent)) {
                return true;
            }
            current = current.getParent();
        } while (current != null);
        return false;
    }

    @Override
    public void start(Stage primaryStage) {
        VBox innerBox = new VBox(15);
        for (int i = 1; i <= 3; i++) {
            innerBox.getChildren().add(new TextField("Field #" + i));
        }

        VBox root = new VBox(15, innerBox, new TextField("Field #4"));
        root.setPadding(new Insets(25));
        root.setAlignment(Pos.CENTER);
        VBox.setVgrow(innerBox, Priority.ALWAYS);

        Scene scene = new Scene(root);
        scene.focusOwnerProperty().addListener((observable, oldNode, newNode) -> {
            if (!isParentAncestorOfNode(innerBox, newNode)) {
                innerBox.getChildren().get(0).requestFocus();
            }
        });

        primaryStage.setScene(scene);
        primaryStage.show();
    }

}

Once the focus attempts to go to the fourth TextField it will instead focus the first TextField. This is absolute. In other words, no matter what (e.g. Tab, clicking with mouse, requesting focus programmatically) the fourth TextField will not be able to be focused—nor will anything not a descendant of innerBox. I'm not sure if that's desired behavior. To disable the behavior simply remove the ChangeListener from the focusOwner property (will require you to keep a reference to the listener). Of course, you can attempt to modify the code to make it more nuanced.

Upvotes: 5

Related Questions