It seems that in FXML if you don't specify a ID (CSS) then the fx:id value is used by default. My previous understanding was the two were completely disjoint, ID for CSS and only CSS. fx:id for @FXML bindings in the controller.
This can be demonstrated with a small test - three buttons, first with ID, second with FX:ID, third with both types of ID.
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Button id="cssid0" mnemonicParsing="false" text="Button" />
<Button fx:id="fxid1" mnemonicParsing="false" text="Button" />
<Button id="cssid2" fx:id="fxid2" mnemonicParsing="false" text="Button" />
</children>
</VBox>
Using Node.lookup(cssSelector) allows selection by fx:id
@Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/foo.fxml"));
Parent p = loader.load();
System.out.println(p.lookup("#cssid0")); // => Button[id=cssid0, styleClass=button]'Button'
System.out.println(p.lookup("#fxid1")); // => Button[id=fxid1, styleClass=button]'Button'
System.out.println(p.lookup("#fxid2")); // => null (if there is a CSS ID it takes precedent)
System.out.println(p.lookup("#cssid2")); // Button[id=cssid2, styleClass=button]'Button'
stage.setScene(new Scene(p, 200, 200));
stage.getScene().getStylesheets().add("/foo.css");
stage.show();
}
Also CSS allows selection by the fx:ID
#cssid0 {
-fx-color: red;
}
#fxid1 {
-fx-color: green;
}
#cssid2 {
-fx-color: blue;
}
This does not appear to be covered by existing question What's the difference between fx:id and id: in JavaFX?, Javadoc for Node.getId() or anywhere else I could find.
This feature is really useful as we only need to only specify a single fx:id which can be used for controllers, CSS and unit testing using test-FX.
Is it OK to use this approach or am I building assumptions on undocumented behaviour that might change in a later release? Or is it documented somewhere I'm missing?
IMO it is partially documented in Introduction to FXML :: Variable Resolution. But not so obvious at first look:
Assigning an fx:id value to an element creates a variable in the document's namespace that can later be referred to by variable dereference attributes ...
Additionally, if the object's type defines an "id" property, this value will also be passed to the objects setId() method.
It should be completed with "if object defines the idProperty and was not set already ...". Based on the related source code is in FXMLLoader at line around 708:
// Add the value to the namespace
if (fx_id != null) {
namespace.put(fx_id, value);
// If the value defines an ID property, set it
IDProperty idProperty = value.getClass().getAnnotation(IDProperty.class);
if (idProperty != null) {
Map<String, Object> properties = getProperties();
// set fx:id property value to Node.id only if Node.id was not
// already set when processing start element attributes
if (properties.get(idProperty.value()) == null) {
properties.put(idProperty.value(), fx_id);
}
}
...
}
I think it is ok to build on this behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With