Is it possible in JavaFX to change the focus traversal policy, like in AWT?
Because the traversal order for two of my HBox
es is wrong.
Is it possible in JavaFX to change the focus traversal policy, like in AWT?
Because the traversal order for two of my HBox
es is wrong.
In common case the navigation is done in a container order, in order of children, or according to arrow keys pressing. You can change order of nodes - it will be the optimal solution for you in this situation.
There is a back door in JFX about traversal engine strategy substitution :
you can subclass the internal class com.sun.javafx.scene.traversal.TraversalEngine
engine = new TraversalEngine(this, false) {
@Override public void trav(Node owner, Direction dir) {
// do whatever you want
}
};
And use
setImpl_traversalEngine(engine);
call to apply that engine.
You can observe the code of OpenJFX, to understand, how it works, and what you can do.
Be very careful : it is an internal API, and it is likely to change, possibly, in the nearest future. So don't rely on this (you cannot rely on this officialy, anyway).
Sample implementation :
public void start(Stage stage) throws Exception {
final VBox vb = new VBox();
final Button button1 = new Button("Button 1");
final Button button2 = new Button("Button 2");
final Button button3 = new Button("Button 3");
TraversalEngine engine = new TraversalEngine(vb, false) {
@Override
public void trav(Node node, Direction drctn) {
int index = vb.getChildren().indexOf(node);
switch (drctn) {
case DOWN:
case RIGHT:
case NEXT:
index++;
break;
case LEFT:
case PREVIOUS:
case UP:
index--;
}
if (index < 0) {
index = vb.getChildren().size() - 1;
}
index %= vb.getChildren().size();
System.out.println("Select <" + index + ">");
vb.getChildren().get(index).requestFocus();
}
};
vb.setImpl_traversalEngine(engine);
vb.getChildren().addAll(button1, button2, button3);
Scene scene = new Scene(vb);
stage.setScene(scene);
stage.show();
}
It will require strong analitical skills for common case ;)
The simplest solution is to edit the FXML file and reorder the containers appropriately. As an example, my current application has a registration dialog in which a serial number can be entered. There are 5 text fields for this purpose. For the focus to pass from one text field to the other correctly, I had to list them in this way:
<TextField fx:id="tfSerial1" layoutX="180.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial2" layoutX="257.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial3" layoutX="335.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial4" layoutX="412.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial5" layoutX="488.0" layoutY="166.0" prefWidth="55.0" />
Bluehair's answer is right, but you can do this even in JavaFX Scene Builder.
You have Hierarchy panel in left column. There are all your components from scene. Their order represents focus traversal order and it responds to their order in FXML file.
I found this tip on this webpage:www.wobblycogs.co.uk
In common case the navigation is done in a container order, in order of children, or according to arrow keys pressing. You can change order of nodes - it will be the optimal solution for you in this situation.
There is a back door in JFX about traversal engine strategy substitution :
you can subclass the internal class com.sun.javafx.scene.traversal.TraversalEngine
engine = new TraversalEngine(this, false) {
@Override public void trav(Node owner, Direction dir) {
// do whatever you want
}
};
And use
setImpl_traversalEngine(engine);
call to apply that engine.
You can observe the code of OpenJFX, to understand, how it works, and what you can do.
Be very careful : it is an internal API, and it is likely to change, possibly, in the nearest future. So don't rely on this (you cannot rely on this officialy, anyway).
Sample implementation :
public void start(Stage stage) throws Exception {
final VBox vb = new VBox();
final Button button1 = new Button("Button 1");
final Button button2 = new Button("Button 2");
final Button button3 = new Button("Button 3");
TraversalEngine engine = new TraversalEngine(vb, false) {
@Override
public void trav(Node node, Direction drctn) {
int index = vb.getChildren().indexOf(node);
switch (drctn) {
case DOWN:
case RIGHT:
case NEXT:
index++;
break;
case LEFT:
case PREVIOUS:
case UP:
index--;
}
if (index < 0) {
index = vb.getChildren().size() - 1;
}
index %= vb.getChildren().size();
System.out.println("Select <" + index + ">");
vb.getChildren().get(index).requestFocus();
}
};
vb.setImpl_traversalEngine(engine);
vb.getChildren().addAll(button1, button2, button3);
Scene scene = new Scene(vb);
stage.setScene(scene);
stage.show();
}
It will require strong analitical skills for common case ;)
We're using JavaFX event filters for this, e.g.:
cancelButton.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.TAB && event.isShiftDown()) {
event.consume();
getDetailsPane().requestFocus();
}
}
});
The event.consume()
suppresses the default focus traversal, which otherwise causes trouble when calling requestFocus()
.
This is the accepted answer adapted to change of internal api (happened at some point of fx-8, my current version is 8u60b5). Obviously the original disclaimer still applies: it's internal api, open to change without notice at any time!
The changes (compared to the accepted answer)
The plain translation of the example code:
/**
* Requirement: configure focus traversal
* old question with old hack (using internal api):
* https://mcmap.net/q/487946/-javafx-how-to-change-the-focus-traversal-policy/203657
*
* New question (closed as duplicate by ... me ..)
* https://mcmap.net/q/503266/-change-javafx-tab-button-orderring-of-textfields-from-quot-left-to-right-quot-to-quot-right-to-left-quot-by-fxml-file-duplicate/203657
* Old hack doesn't work, change of internal api
* rewritten to new internal (sic!) api
*
*/
public class FocusTraversal extends Application {
private Parent getContent() {
final VBox vb = new VBox();
final Button button1 = new Button("Button 1");
final Button button2 = new Button("Button 2");
final Button button3 = new Button("Button 3");
Algorithm algo = new Algorithm() {
@Override
public Node select(Node node, Direction dir,
TraversalContext context) {
Node next = trav(node, dir);
return next;
}
/**
* Just for fun: implemented to invers reaction
*/
private Node trav(Node node, Direction drctn) {
int index = vb.getChildren().indexOf(node);
switch (drctn) {
case DOWN:
case RIGHT:
case NEXT:
case NEXT_IN_LINE:
index--;
break;
case LEFT:
case PREVIOUS:
case UP:
index++;
}
if (index < 0) {
index = vb.getChildren().size() - 1;
}
index %= vb.getChildren().size();
System.out.println("Select <" + index + ">");
return vb.getChildren().get(index);
}
@Override
public Node selectFirst(TraversalContext context) {
return vb.getChildren().get(0);
}
@Override
public Node selectLast(TraversalContext context) {
return vb.getChildren().get(vb.getChildren().size() - 1);
}
};
ParentTraversalEngine engine = new ParentTraversalEngine(vb, algo);
// internal api in fx8
// vb.setImpl_traversalEngine(engine);
// internal api since fx9
ParentHelper.setTraversalEngine(vb, engine);
vb.getChildren().addAll(button1, button2, button3);
return vb;
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
focusTraversable="false"
. It will continue to the next focusable element. –
Salesgirl In scene builder go to the view menu and select show document. on the left will be all objects in your current fxml document. drag controls up or down in the list to reorder for tab index. Select hide document to use the other tools since the document pane hogs the space.
I used Eventfilter as solution in combination with referencing the field ID's, so all you have to do is name the fields like (field1,field2,field3,field4) so you can place the fields where u want:
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, (event) -> {
if(event.getCode().equals(KeyCode.TAB)){
event.consume();
final Node node = mainScene.lookup("#field"+focusNumber);
if(node!=null){
node.requestFocus();
}
focusNumber ++;
if(focusNumber>11){
focusNumber=1;
}
}
});
You can do this easily with SceneBuilder. Open fxml file with scenebuilder and go to hierarchy under Document tab. Place input controls into a order you want by dragging input controls. Remember to check Focus Traversal in properties. Then focus traversal policy will work nicely when you press tab bar.
You can use NodeName.requestFocus()
as said above; furthermore, make sure you request this focus after instantiating and adding all of your nodes for the root layout, because as you do so, the focus will be changing.
I had a problem in which a JavaFX Slider was capturing right and left arrow keystrokes that I wanted to be handled by my method keyEventHandler
(which handled key events for the Scene). What worked for me was to add the following line to the code that initialized the Slider:
slider.setOnKeyPressed(keyEventHandler);
and to add
keyEvent.consume();
at the end of keyEventHandler
.
General solution inspired by Patrick Eckert's answer.
When I am creating the UI, say for example adding a TextField
, I set things up like so:
List<String> displayOrder;
Map<String, Node> cycle;
TextField tf = new TextField();
tf.setId("meTF");
cycle.put("meTF", tf);
displayOrder.add("meTF");
getChildren().add(tf);
Then on the UI element (layout usually) you add this:
ui.addEventFilter(KeyEvent.KEY_PRESSED, (ke) ->
{
if(!ke.getCode().equals(KeyCode.TAB) || !(ke.getTarget() instanceof Node))
return;
int i = displayOrder.indexOf(((Node)ke.getTarget()).getId());
if(i < 0) // can't find it
return;
if(ke.isShiftDown())
i = (i == 0 ? displayOrder.size() - 1 : i - 1);
else
i = ++i % displayOrder.size();
cycle.get(displayOrder.get(i)).requestFocus();
ke.consume();
});
FYI I think it is important to only consume the event if you are actually going to call request focus on something. Less likely to break something unintentionally that way...
If anyone can think of ways to optimize this further I'd appreciate knowing :)
© 2022 - 2024 — McMap. All rights reserved.