Reputation: 762
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 TextField
s 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
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