28

JavaFX 中是否可以像在 AWT 中那样更改焦点遍历策略?

因为我的两个HBoxes的遍历顺序是错误的。

4

11 回答 11

21

最简单的解决方案是编辑 FXML 文件并适当地重新排序容器。例如,我当前的应用程序有一个注册对话框,可以在其中输入序列号。为此目的有 5 个文本字段。为了使焦点正确地从一个文本字段传递到另一个,我必须以这种方式列出它们:

<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" />
于 2013-05-27T15:50:52.250 回答
16

Bluehair 的回答是正确的,但您甚至可以在 JavaFX Scene Builder 中执行此操作。

您在左栏中有层次结构面板。场景中有你所有的组件。它们的顺序代表焦点遍历顺序,它响应它们在 FXML 文件中的顺序。

我在这个网页上找到了这个提示:www.wobblycogs.co.uk

于 2013-12-09T18:03:46.343 回答
12

通常情况下,导航是按容器顺序、子顺序或按箭头键完成的。您可以更改节点的顺序 - 在这种情况下,这将是您的最佳解决方案。

JFX 中有一个关于遍历引擎策略替换的后门:

你可以继承内部类 com.sun.javafx.scene.traversal.TraversalEngine

engine = new TraversalEngine(this, false) {
            @Override public void trav(Node owner, Direction dir) {
                // do whatever you want
            }
        };

并使用

setImpl_traversalEngine(engine); 

调用应用该引擎。

您可以观察 OpenJFX 的代码,了解它是如何工作的,以及您可以做什么。

要非常小心:它是一个内部 API,它可能会在最近的将来发生变化。所以不要依赖这个(无论如何你不能依赖这个官方)。

示例实现:

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();
}

对于普通情况,这将需要强大的分析技能;)

于 2013-03-06T14:37:06.953 回答
9

我们为此使用JavaFX 事件过滤器,例如:

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();
        }
    }
});

禁止event.consume()默认焦点遍历,否则在调用时会导致麻烦requestFocus()

于 2014-07-02T10:20:06.900 回答
7

这是适用于更改内部 api 的公认答案(发生在 fx-8 的某个时间点,我当前的版本是 8u60b5)。显然原来的免责声明仍然适用:它是内部api,随时开放更改,恕不另行通知!

更改(与接受的答案相比)

  • Parent 需要一个 ParentTraversalEngine 类型的 TraversalEngine
  • nav 不再是 TraversalEngine(也不是 ParentTE)的方法,而只是 TopLevelTraversalEngine
  • 导航实现被委托给称为算法的策略
  • 实际焦点转移(似乎是?)由 TopLevelTE 处理,算法只找到并返回新目标

示例代码的简单翻译:

/**
 * Requirement: configure focus traversal
 * old question with old hack (using internal api):
 * http://stackoverflow.com/q/15238928/203657
 * 
 * New question (closed as duplicate by ... me ..)
 * http://stackoverflow.com/q/30094080/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);
    }
}
于 2015-05-07T10:34:53.090 回答
1

在场景构建器中,转到视图菜单并选择显示文档。左侧将是当前 fxml 文档中的所有对象。在列表中向上或向下拖动控件以重新排序选项卡索引。选择隐藏文档以使用其他工具,因为文档窗格占用空间。

于 2014-05-06T05:35:54.303 回答
1

我使用 Eventfilter 作为解决方案并结合引用字段 ID,因此您所要做的就是将字段命名为 (field1,field2,field3,field4) 这样您就可以将字段放置在您想要的位置:

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;
            }
        }
    }); 
于 2016-02-22T13:23:11.450 回答
1

您可以使用SceneBuilder轻松完成此操作。使用 scenebuilder 打开 fxml 文件并转到Document选项卡下的层次结构。通过拖动输入控件将输入控件放置到您想要的顺序。记得在属性中检查焦点遍历。然后,当您按下标签栏时,焦点遍历策略将很好地工作。

使用 SB 更改焦点遍历策略

于 2019-04-09T05:50:55.363 回答
0

您可以NodeName.requestFocus()如上所述使用;此外,请确保在为根布局实例化并添加所有节点之后请求此焦点,因为这样做时,焦点将发生变化。

于 2018-05-03T22:06:51.027 回答
0

我遇到了一个问题,JavaFX Slider 正在捕获我希望通过我的方法处理的左右箭头键击keyEventHandler(它处理场景的键事件)。对我有用的是将以下行添加到初始化 Slider 的代码中:

slider.setOnKeyPressed(keyEventHandler);

并添加

keyEvent.consume();

在结束时keyEventHandler

于 2020-05-29T16:18:50.573 回答
0

受 Patrick Eckert 回答启发的通用解决方案。

当我创建 UI 时,例如添加一个TextField,我设置如下:

List<String> displayOrder;
Map<String, Node> cycle;

TextField tf = new TextField();
tf.setId("meTF");
cycle.put("meTF", tf);
displayOrder.add("meTF");
getChildren().add(tf);

然后在 UI 元素(通常是布局)上添加:

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();
});

仅供参考,我认为仅在您实际上要调用请求焦点时才使用该事件很重要。不太可能以这种方式无意中破坏某些东西......

如果有人能想出进一步优化的方法,我将不胜感激:)

于 2021-08-10T06:20:06.100 回答