From the docs it seems like it should be possible to use simple expressions when calculating Element properties but it always seems to give errors. Is the syntax here correct and is there any more guidance available?
A small example (this is based on the javafx-archetype-fxml
linked to from here):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.geometry.Insets?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.nowhere.PrimaryController">
<children>
<!-- WORKS -->
<Label text="${source.length}" />
<!-- ERROR -->
<Label text="${source.length*2}" />
<Button fx:id="primaryButton" text="Switch to Secondary View" onAction="#switchToSecondary"/>
<TextField fx:id="source" />
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
This is intended to update the Label text with the length of text in the TextField (this is just as an example)
A single variable ${source.length}
is OK but an expression ${source.length*2}
is not. It seems that any binary or unary expression is problematic.
The error I see includes this stack trace:
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Number.longValue()" because "<parameter1>" is null
at javafx.fxml@24.0.1/com.sun.javafx.fxml.expression.Expression.lambda$multiply$2(Expression.java:932)
at javafx.fxml@24.0.1/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
at javafx.fxml@24.0.1/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:191)
at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:64)
at javafx.base@24.0.1/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:57)
at javafx.fxml@24.0.1/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:200)
at javafx.base@24.0.1/javafx.beans.property.StringPropertyBase.bind(StringPropertyBase.java:171)
at javafx.fxml@24.0.1/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:318)
at javafx.fxml@24.0.1/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:235)
at javafx.fxml@24.0.1/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:767)
at javafx.fxml@24.0.1/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2939)
at javafx.fxml@24.0.1/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2624)
... 11 more
In the source for com.sun.javafx.binding.ExpressionHelper
(line 64) it looks like addListener
tries to get the value of the observable (comment says "validate observable") but at this point the TextField has not been fully created and therefore its length evaluates to null, and the binary expression null * 2 fails. That's my understanding, could be wrong.
I'm using Java 23 and JFX 24.0.1.
I think your analysis is correct; in my opinion this is a bug.
You can workaround this by defining the text field before you reference it in the FXML, using <fx:define>
and <fx:reference>
:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.geometry.Insets?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.exprbinding.PrimaryController">
<fx:define><TextField fx:id="source" /></fx:define>
<children>
<!-- WORKS -->
<Label text="${source.length}" />
<!-- NOW ALSO WORKING -->
<Label text="${source.length*2}" />
<Button fx:id="primaryButton" text="Switch to Secondary View" onAction="#switchToSecondary"/>
<fx:reference source="source" />
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>