JavaFX:如何更改焦点遍历策略?
Posted
技术标签:
【中文标题】JavaFX:如何更改焦点遍历策略?【英文标题】:JavaFX: How to change the focus traversal policy? 【发布时间】:2013-02-20 17:55:41 【问题描述】:在 JavaFX 中是否可以像在 AWT 中那样更改 焦点遍历策略?
因为我的两个HBox
es 的遍历顺序是错误的。
【问题讨论】:
你可以通过调用node.requestFocus()方法为javafx中的任意节点请求焦点 是的,我知道,但这不是一个很好的解决方案。 请明确您的问题并提供sscce。 最好将控件分组到“遍历组”中并设置选项卡索引。如果此遍历组中的某个控件具有焦点,则按下 [Tab]-Key 会强制具有下一个更高索引的控件成为焦点。类似于按 [Tab] + [Shift] 和下一个较低的索引。 @gontard,提出问题,以便有答案。 Sonja 在问,是否可以自定义焦点遍历方式。 【参考方案1】:最简单的解决方案是编辑 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" />
【讨论】:
是的,这是最简单的方法。我遇到过同样的问题。这意味着在我们设计 UI Scene Builder 时,会按附加顺序重新生成相关的 FXML 标签。一旦我们重新排列它们正确的顺序,它就可以正常工作了。 如果 SB 能自动为我们做这件事就好了。 这绝对是最简单的方法。谢谢。【参考方案2】:Bluehair 的回答是对的,但你甚至可以在 JavaFX Scene Builder 中做到这一点。
您在左栏中有层次结构面板。场景中有你所有的组件。它们的顺序代表焦点遍历顺序,它响应它们在 FXML 文件中的顺序。
我在这个网页上找到了这个提示:www.wobblycogs.co.uk
【讨论】:
【参考方案3】:通常情况下,导航是按容器顺序、子项顺序或按箭头键完成的。您可以更改节点的顺序 - 这将是您在这种情况下的最佳解决方案。
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();
一般情况下需要强大的分析技能;)
【讨论】:
fx8 中的内部 api 似乎发生了很大变化,不再可能(可能需要通过实现自定义算法进行调整,用它配置 ParentFocusTraversalEngine)【参考方案4】:我们为此使用JavaFX event filters,例如:
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()
时造成麻烦。
【讨论】:
这是最简单最好的。谢谢。 在我看来,这是唯一可持续的答案。我们不调用内部 API 并且在 FXML 中重新排列节点并不总是可能的,例如如果我们想使用 Java 动态添加节点。 即使对于 GirdPane 中的控件以及使用 Java 代码动态生成的控件也是最合适的答案【参考方案5】:这是适应内部 api 更改的公认答案(发生在 fx-8 的某个时间点,我当前的版本是 8u60b5)。显然原来的免责声明仍然适用:它是 internal api,随时开放更改,恕不另行通知!
变化(与接受的答案相比)
Parent 需要一个 ParentTraversalEngine 类型的 TraversalEngine nav 不再是 TraversalEngine(也不是 ParentTE)的方法,而只是 TopLevelTraversalEngine 的方法 导航实现委托给称为算法的策略 实际焦点转移(似乎是?)由 TopLevelTE 处理,算法只找到并返回新目标示例代码的简单翻译:
/**
* Requirement: configure focus traversal
* old question with old hack (using internal api):
* http://***.com/q/15238928/203657
*
* New question (closed as duplicate by ... me ..)
* http://***.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);
【讨论】:
查看 ContainerTabOrder 以获取完整算法的示例(需要在子级中递归)。但这对我来说太复杂了,我使用了一种解决方法来忽略选项卡策略中的按钮,在没有 java 代码的 FXML 中:设置focusTraversable="false"
。它将继续到下一个可聚焦的元素。
从 Java 9 开始,所有这些内部 API 都没有被导出,并且这个后门不再可用。
如果您被允许修改封装,它们仍然可用 - 这与以前版本中的脏度大致相同:)
嗯 .. 里程有所不同,但 .. 添加出口/打开并不是什么大问题,IMO :)
不讨论自定义 fx 分发 - 只需根据需要使用 --add-exports / --add-opens 修改构建/运行时路径 ...【参考方案6】:
在场景构建器中,转到视图菜单并选择显示文档。左侧将是当前 fxml 文档中的所有对象。在列表中向上或向下拖动控件以重新排序选项卡索引。选择隐藏文档以使用其他工具,因为文档窗格占用空间。
【讨论】:
【参考方案7】:我使用 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;
);
【讨论】:
这是我的通用解决方案的核心。所有 Node 对象都有一个 id,每当我创建一个可聚焦元素时,我也会设置 id。然后,我创建了一个简单的列表,其中包含我想要包含在焦点循环中的元素的 ID 和节点对象的 Map。然后我将获取关键事件的目标 (ke.getTarget()) 获取它的 id,在 id 列表中找到该 id 的索引,根据需要获取上一个/下一个 id 并在 Map 中查找并调用 requestFocus ()关于那个。可能有更好的方法来优化这一点,这很容易理解 - 我认为:)【参考方案8】:您可以使用 SceneBuilder 轻松完成此操作。使用 scenebuilder 打开 fxml 文件,然后转到 Document 选项卡下的 hierarchy。通过拖动输入控件将输入控件放置到您想要的顺序。记得在属性中检查焦点遍历。然后,当您按下标签栏时,焦点遍历策略将很好地工作。
【讨论】:
【参考方案9】:如上所述,您可以使用NodeName.requestFocus()
;此外,请确保在为根布局实例化并添加所有节点后请求此焦点,因为这样做时,焦点将发生变化。
【讨论】:
【参考方案10】:我遇到了一个问题,其中 JavaFX 滑块正在捕获我希望通过我的方法 keyEventHandler
(它处理场景的键事件)处理的左右箭头键击。对我有用的是将以下行添加到初始化 Slider 的代码中:
slider.setOnKeyPressed(keyEventHandler);
添加
keyEvent.consume();
在keyEventHandler
的末尾。
【讨论】:
【参考方案11】:受 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();
);
仅供参考,我认为仅在您实际上要调用请求焦点时才使用该事件很重要。不太可能以这种方式无意中破坏某些东西......
如果有人能想出进一步优化的方法,我将不胜感激:)
【讨论】:
以上是关于JavaFX:如何更改焦点遍历策略?的主要内容,如果未能解决你的问题,请参考以下文章