JavaFX,通过 Id 查找节点
JavaFX, finding node by Id
我最近遇到了问题,我需要从 window 中使用的节点中获取特定节点(最好是通过 id)。我正在使用 FXMLLoader,所以第一个想法是搜索树结构 FXMLLoader returns.
root|
|
|--some_container |- child1
| |- child2
|
|--other_container |-child3
...
进一步的研究使我在 Scene class 中找到了方法 lookup,但是官方文档 (https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Scene.html#lookup-java.lang.String-)缺乏对这种方法如何工作的描述,并且在我看来是不准确的(我费了很大力气才使它起作用)。
如此处所述 (How to find an element with an ID in JavaFX?) 方法 lookup 的调用必须是 after 方法 show,为什么?
第二个问题是为什么节点id前面必须有'#'(井号)?虽然官方文档指出它应该是“...CSS 选择器...”? (我在这里有点困惑,因为 class Node 只包含一个 id-like 属性)
Edi1 - @kleopatra 的简单示例 虽然问题不是严格意义上的代码,但它与理解有关。
具有场景内容的示例 fxml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1" fx:controller="display.windows.ConsoleForBoard">
<children>
<SplitPane fx:id="mainSplitPane" dividerPositions="0.7" layoutX="277.0" layoutY="100.0" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="398.0" prefWidth="404.0">
<children>
<SplitPane dividerPositions="0.8" layoutX="150.0" layoutY="53.0" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Canvas fx:id="drawBoard" height="313.0" layoutX="125.0" layoutY="43.0" width="413.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TextArea layoutX="107.0" layoutY="-86.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<ToggleButton layoutX="35.0" layoutY="62.0" mnemonicParsing="false" text="ToggleButton" />
<TextField alignment="CENTER" layoutX="2.0" layoutY="14.0" promptText="Main Menu" text="Main Menu" />
</children>
</AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
注意: Canvas 有 fx:id="drawBoard"
创建此场景的示例class:
public class BoardWithMenu extends Application {
Scene mainScene;
AnchorPane root;
FXMLLoader loader;
public static void start() {launch("start");}
@Override
public void start(Stage stage) throws Exception {
loader = new FXMLLoader(this.getClass().getResource("scene_definition.fxml"));
root = loader.load();
mainScene = new Scene(root, 500, 500);
stage.setScene(mainScene);
stage.setTitle("Space map");
stage.show();
Canvas board = (Canvas) mainScene.lookup("drawBoard");
System.out.println(board.getId());
}
}
问题:
行
Canvas board = (Canvas) mainScene.lookup("#drawBoard");
之前不能调用
stage.show();
它会产生空指针错误。为什么?
您引用的评论:
As mentioned here (How to find an element with an ID in JavaFX?)
invocation of method lookup must be after method show?
笼统的说法是错误的。加载后是否可以立即查找节点取决于添加方式:
- 所有作为子节点注入的节点在加载器之后直接可用(f.i。“#mainSplitPane”作为根的子节点)
- 所有由皮肤插入的节点只有在附加皮肤后才可用,即显示后(f.i。“#drawBoard”注入到splitPane的项目中)
此答案是对 的补充。
使用 Java 变量引用而不是查找,除非你真的需要查找
我的建议是尽可能避免使用基于 CSS 的 lookup 方法(支持 Java 中的直接类型引用)。
此建议在使用 FXML 时尤其适用。 FXML 有能力给一个节点分配一个fx:id
,可以是mapped to a named, typed field in a Controller (using the @FXML
注解)。通常,当涉及 FXML 时,这是通过 ID 引用节点的首选方法。由于多种原因,它是首选:
- 当使用 Java 变量引用而不是查找时,您有类型化信息和编译时检查,而不是非类型化运行时检查。
- 查找可能对时间敏感:正确的结果可能需要布局传递 and/or CSS 应用程序。
- 在布局传递和渲染阶段,Java控件皮肤的 FX 框架实现可能会添加或删除各种节点,这会改变查找结果。
- 使用查找,您可能会找到已通过私有皮肤实现添加到场景中的节点。然后编写依赖于那些找到的节点的代码,你就破坏了封装。稍后的 JavaFX 版本可能会更改私有皮肤实现,这会更改皮肤添加的结构或节点,然后破坏您的代码。
以上几点可以使使用查找而不是依赖 Java 变量引用时更容易编码错误。
理解id和id的区别fx:id
不要将 FXML 中使用的 fx:id
与 CSS ID 混淆,它们是不同的东西,尽管您可能经常希望将它们指定为相同的值,但您可以在FXML 中元素的定义,例如:
<Button fx:id="myButton" id="myButton">
fx:id
值将用于映射到具有 @FXML
注释的控制器中的变量引用(或者如果未提供注释,则通过对 public 成员的反射) :
@FXML Button myButton;
id
值将映射到存储在节点中并与之关联的 id 。相关文档是这样描述的:
The id of this Node. This simple string identifier is useful for finding a specific Node within the scene graph. While the id of a Node should be unique within the scene graph, this uniqueness is not enforced. This is analogous to the "id" attribute on an HTML element (CSS ID Specification).
查找基于 CSS 选择器
这是节点查找文档中按 ID 查找的示例:
- 例如,如果给定一个Node的id为“myId”,那么可以使用如下查找方法来查找这个节点:
scene.lookup("#myId");
.
可以在任何节点或场景中调用查找。
节点lookup(selector)
methods work based on the node id
, not the fx:id
because the lookup string is based on css. The lookup uses a css selector。因此,基于 ID 的查找将在 ID 前面有一个 #
前缀,因为这是通过 ID 选择节点的语法。您不仅限于按 ID 查找。 CSS 选择器语法非常强大,它本身几乎就是一种语言,因此您可以使用复杂的表达式来查询场景图节点树并根据需要提取匹配节点。
注意皮肤实现可以修改场景图
如果你确实想做一个 jquery 风格 find(这是 JavaFX 方法 lookup
方法的一种),那么要格外小心由于 kleopatra 在皮肤插入的节点上的第二点,你何时以及如何做到这一点:
- all nodes that are inserted by the skin are available only after the skin is attached, that is after showing (f.i. "#drawBoard" which is injected to the splitPane's items)
另外请注意,可以通过控件的 fx-skin
属性在 css 中应用皮肤。因此,即使 CSS 的应用程序也可以更改皮肤,这可以更改可以更改查找结果的场景图。
如何确保在查找时找到预期的节点
为确保所有预期的节点都在要查找的场景中并应用了预期的属性(例如大小、颜色)等,在 进行查找之前:
首先,将所需的节点添加到场景中。
其次,执行以下操作之一:
- Generate a layout pass 或
- show 相关阶段(这将导致布局通过)或
- 在
Platform.runLater()
块中进行查找。
- 这将等待下一个脉冲,因此在执行之前完成当前脉冲的布局。虽然
Platform.runLater()
规范不能保证这一点,但实际上,它就是这样工作的。
在大多数情况下,我的首选解决方案是生成布局过程。
要生成布局过程:
- 将所有必需的节点添加到具有适当 CSS 样式的场景中 AND
- 在场景根或适当的父节点上调用
applyCss()
and layout()
方法。
然后调用所需的查找方法来查找节点。
没有显式布局传递的查找(小心)
如果您知道您要查找的元素已经在场景中,那么您可以直接lookup()
节点,它们就会被找到。例如,如果您只是在之前的一段代码中明确地将它们添加到场景中,那么您就知道它们不需要由皮肤在后续脉冲中创建。
但是,在执行布局之前,查找的节点可能不会 return 某些属性(例如高度或宽度)的正确初始化值。同样,在 CSS 应用于节点之前,由 CSS 设置的查找节点的任何属性都可能不正确。因此,为了安全起见,我的建议是谨慎行事,并确保在尝试查找之前将布局和 CSS 应用于您要查找的节点。
总结
从这个答案可以得出,JavaFX lookup()
方法的使用带有一些警告和注意事项。这就是主要建议避免使用它们的原因,除非它们为您的特定应用程序带来很大好处。
请注意,查找可能有很大的好处:它们非常强大,一些 jquery 开发人员使用类似的功能实现的效果令人难以置信,请小心使用它们。
我最近遇到了问题,我需要从 window 中使用的节点中获取特定节点(最好是通过 id)。我正在使用 FXMLLoader,所以第一个想法是搜索树结构 FXMLLoader returns.
root|
|
|--some_container |- child1
| |- child2
|
|--other_container |-child3
...
进一步的研究使我在 Scene class 中找到了方法 lookup,但是官方文档 (https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Scene.html#lookup-java.lang.String-)缺乏对这种方法如何工作的描述,并且在我看来是不准确的(我费了很大力气才使它起作用)。
如此处所述 (How to find an element with an ID in JavaFX?) 方法 lookup 的调用必须是 after 方法 show,为什么?
第二个问题是为什么节点id前面必须有'#'(井号)?虽然官方文档指出它应该是“...CSS 选择器...”? (我在这里有点困惑,因为 class Node 只包含一个 id-like 属性)
Edi1 - @kleopatra 的简单示例 虽然问题不是严格意义上的代码,但它与理解有关。
具有场景内容的示例 fxml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1" fx:controller="display.windows.ConsoleForBoard">
<children>
<SplitPane fx:id="mainSplitPane" dividerPositions="0.7" layoutX="277.0" layoutY="100.0" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="398.0" prefWidth="404.0">
<children>
<SplitPane dividerPositions="0.8" layoutX="150.0" layoutY="53.0" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<Canvas fx:id="drawBoard" height="313.0" layoutX="125.0" layoutY="43.0" width="413.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TextArea layoutX="107.0" layoutY="-86.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<ToggleButton layoutX="35.0" layoutY="62.0" mnemonicParsing="false" text="ToggleButton" />
<TextField alignment="CENTER" layoutX="2.0" layoutY="14.0" promptText="Main Menu" text="Main Menu" />
</children>
</AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
注意: Canvas 有 fx:id="drawBoard"
创建此场景的示例class:
public class BoardWithMenu extends Application {
Scene mainScene;
AnchorPane root;
FXMLLoader loader;
public static void start() {launch("start");}
@Override
public void start(Stage stage) throws Exception {
loader = new FXMLLoader(this.getClass().getResource("scene_definition.fxml"));
root = loader.load();
mainScene = new Scene(root, 500, 500);
stage.setScene(mainScene);
stage.setTitle("Space map");
stage.show();
Canvas board = (Canvas) mainScene.lookup("drawBoard");
System.out.println(board.getId());
}
}
问题:
行
Canvas board = (Canvas) mainScene.lookup("#drawBoard");
之前不能调用
stage.show();
它会产生空指针错误。为什么?
您引用的评论:
As mentioned here (How to find an element with an ID in JavaFX?) invocation of method lookup must be after method show?
笼统的说法是错误的。加载后是否可以立即查找节点取决于添加方式:
- 所有作为子节点注入的节点在加载器之后直接可用(f.i。“#mainSplitPane”作为根的子节点)
- 所有由皮肤插入的节点只有在附加皮肤后才可用,即显示后(f.i。“#drawBoard”注入到splitPane的项目中)
此答案是对
使用 Java 变量引用而不是查找,除非你真的需要查找
我的建议是尽可能避免使用基于 CSS 的 lookup 方法(支持 Java 中的直接类型引用)。
此建议在使用 FXML 时尤其适用。 FXML 有能力给一个节点分配一个fx:id
,可以是mapped to a named, typed field in a Controller (using the @FXML
注解)。通常,当涉及 FXML 时,这是通过 ID 引用节点的首选方法。由于多种原因,它是首选:
- 当使用 Java 变量引用而不是查找时,您有类型化信息和编译时检查,而不是非类型化运行时检查。
- 查找可能对时间敏感:正确的结果可能需要布局传递 and/or CSS 应用程序。
- 在布局传递和渲染阶段,Java控件皮肤的 FX 框架实现可能会添加或删除各种节点,这会改变查找结果。
- 使用查找,您可能会找到已通过私有皮肤实现添加到场景中的节点。然后编写依赖于那些找到的节点的代码,你就破坏了封装。稍后的 JavaFX 版本可能会更改私有皮肤实现,这会更改皮肤添加的结构或节点,然后破坏您的代码。
以上几点可以使使用查找而不是依赖 Java 变量引用时更容易编码错误。
理解id和id的区别fx:id
不要将 FXML 中使用的 fx:id
与 CSS ID 混淆,它们是不同的东西,尽管您可能经常希望将它们指定为相同的值,但您可以在FXML 中元素的定义,例如:
<Button fx:id="myButton" id="myButton">
fx:id
值将用于映射到具有 @FXML
注释的控制器中的变量引用(或者如果未提供注释,则通过对 public 成员的反射) :
@FXML Button myButton;
id
值将映射到存储在节点中并与之关联的 id 。相关文档是这样描述的:
The id of this Node. This simple string identifier is useful for finding a specific Node within the scene graph. While the id of a Node should be unique within the scene graph, this uniqueness is not enforced. This is analogous to the "id" attribute on an HTML element (CSS ID Specification).
查找基于 CSS 选择器
这是节点查找文档中按 ID 查找的示例:
- 例如,如果给定一个Node的id为“myId”,那么可以使用如下查找方法来查找这个节点:
scene.lookup("#myId");
.
可以在任何节点或场景中调用查找。
节点lookup(selector)
methods work based on the node id
, not the fx:id
because the lookup string is based on css. The lookup uses a css selector。因此,基于 ID 的查找将在 ID 前面有一个 #
前缀,因为这是通过 ID 选择节点的语法。您不仅限于按 ID 查找。 CSS 选择器语法非常强大,它本身几乎就是一种语言,因此您可以使用复杂的表达式来查询场景图节点树并根据需要提取匹配节点。
注意皮肤实现可以修改场景图
如果你确实想做一个 jquery 风格 find(这是 JavaFX 方法 lookup
方法的一种),那么要格外小心由于 kleopatra 在皮肤插入的节点上的第二点,你何时以及如何做到这一点:
- all nodes that are inserted by the skin are available only after the skin is attached, that is after showing (f.i. "#drawBoard" which is injected to the splitPane's items)
另外请注意,可以通过控件的 fx-skin
属性在 css 中应用皮肤。因此,即使 CSS 的应用程序也可以更改皮肤,这可以更改可以更改查找结果的场景图。
如何确保在查找时找到预期的节点
为确保所有预期的节点都在要查找的场景中并应用了预期的属性(例如大小、颜色)等,在 进行查找之前:
首先,将所需的节点添加到场景中。
其次,执行以下操作之一:
- Generate a layout pass 或
- show 相关阶段(这将导致布局通过)或
- 在
Platform.runLater()
块中进行查找。- 这将等待下一个脉冲,因此在执行之前完成当前脉冲的布局。虽然
Platform.runLater()
规范不能保证这一点,但实际上,它就是这样工作的。
- 这将等待下一个脉冲,因此在执行之前完成当前脉冲的布局。虽然
在大多数情况下,我的首选解决方案是生成布局过程。
要生成布局过程:
- 将所有必需的节点添加到具有适当 CSS 样式的场景中 AND
- 在场景根或适当的父节点上调用
applyCss()
andlayout()
方法。
然后调用所需的查找方法来查找节点。
没有显式布局传递的查找(小心)
如果您知道您要查找的元素已经在场景中,那么您可以直接lookup()
节点,它们就会被找到。例如,如果您只是在之前的一段代码中明确地将它们添加到场景中,那么您就知道它们不需要由皮肤在后续脉冲中创建。
但是,在执行布局之前,查找的节点可能不会 return 某些属性(例如高度或宽度)的正确初始化值。同样,在 CSS 应用于节点之前,由 CSS 设置的查找节点的任何属性都可能不正确。因此,为了安全起见,我的建议是谨慎行事,并确保在尝试查找之前将布局和 CSS 应用于您要查找的节点。
总结
从这个答案可以得出,JavaFX lookup()
方法的使用带有一些警告和注意事项。这就是主要建议避免使用它们的原因,除非它们为您的特定应用程序带来很大好处。
请注意,查找可能有很大的好处:它们非常强大,一些 jquery 开发人员使用类似的功能实现的效果令人难以置信,请小心使用它们。