Reputation: 99
I am trying to create a JavaFX app for multiple screen resolutions (also high resolutions).
I want to use variables/calculations in my FXML file (JavaFX project). This works when I only do the calculations in prefWidth, prefHeight etc only. When trying to do a calculation in (for example) a AnchorPane.topAnchor it gives me the following error.
-- exec-maven-plugin:1.2.1:exec (default-cli) @ SecureChat ---
dec 04, 2014 10:13:50 AM securechat.helpers.CustomStage initialize
SEVERE: null
javafx.fxml.LoadException: Cannot bind to static property.
file:/D:/private_java/SecureChat/target/SecureChat-1.0-SNAPSHOT.jar!/fxml/main.fxml:24
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2591)
at javafx.fxml.FXMLLoader.access$100(FXMLLoader.java:104)
at javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:291)
at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:771)
at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2817)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2526)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2435)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2403)
at securechat.helpers.CustomStage.initialize(CustomStage.java:111)
at securechat.helpers.CustomStage.<init>(CustomStage.java:104)
at securechat.MainApp.start(MainApp.java:40)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$153(LauncherImpl.java:821)
at com.sun.javafx.application.LauncherImpl$$Lambda$51/967161415.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$166(PlatformImpl.java:323)
at com.sun.javafx.application.PlatformImpl$$Lambda$45/584634336.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$164(PlatformImpl.java:292)
at com.sun.javafx.application.PlatformImpl$$Lambda$47/1040960283.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$165(PlatformImpl.java:291)
at com.sun.javafx.application.PlatformImpl$$Lambda$46/501263526.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$141(WinApplication.java:102)
at com.sun.glass.ui.win.WinApplication$$Lambda$37/96639997.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:363)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:303)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:875)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$147(LauncherImpl.java:157)
at com.sun.javafx.application.LauncherImpl$$Lambda$48/815033865.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException: Root cannot be null
at javafx.scene.Scene.<init>(Scene.java:313)
at javafx.scene.Scene.<init>(Scene.java:181)
at securechat.helpers.CustomStage.initialize(CustomStage.java:120)
at securechat.helpers.CustomStage.<init>(CustomStage.java:104)
at securechat.MainApp.start(MainApp.java:40)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$153(LauncherImpl.java:821)
at com.sun.javafx.application.LauncherImpl$$Lambda$51/967161415.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$166(PlatformImpl.java:323)
at com.sun.javafx.application.PlatformImpl$$Lambda$45/584634336.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$164(PlatformImpl.java:292)
at com.sun.javafx.application.PlatformImpl$$Lambda$47/1040960283.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$165(PlatformImpl.java:291)
at com.sun.javafx.application.PlatformImpl$$Lambda$46/501263526.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$141(WinApplication.java:102)
at com.sun.glass.ui.win.WinApplication$$Lambda$37/96639997.run(Unknown Source)
... 1 more
Exception running application securechat.MainApp
------------------------------------------------------------------------
BUILD FAILURE
------------------------------------------------------------------------
Total time: 9.363s
Finished at: Thu Dec 04 10:13:51 CET 2014
Final Memory: 19M/265M
------------------------------------------------------------------------
Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:exec (default-cli) on project SecureChat: Command execution failed. Process exited with an error: 1 (Exit value: 1) -> [Help 1]
To see the full stack trace of the errors, re-run Maven with the -e switch.
Re-run Maven using the -X switch to enable full debug logging.
For more information about the errors and possible solutions, please read the following articles:
[Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
When I remove the AnchorPane.leftAnchor="${dpi.value*200}" (in the last AnchorPane) in Main.fxml it does work.
I am using the following code snippets.
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import securechat.helpers.Scaling?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="securechat.controllers.MainController">
<children>
<fx:define>
<Scaling fx:id="dpi"></Scaling>
</fx:define>
<AnchorPane fx:id="chatsAnchorPane" maxWidth="${dpi.value*200}" minWidth="${dpi.value*200}" prefWidth="${dpi.value*200}" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="openNewChatWindowButton" onAction="#openNewChatDialog" text="%button.openNewChatDialog" prefHeight="${dpi.value*40}" maxHeight="${dpi.value*40}" minHeight="${dpi.value*40}" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<ListView fx:id="chatsListView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="40.0" />
</children>
</AnchorPane>
<AnchorPane fx:id="chatAnchorPane" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="${dpi.value*200}" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<ListView fx:id="chatMessagesListView" AnchorPane.bottomAnchor="40.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<TextField fx:id="chatMessageTextField" onAction="#sendChatMessage" promptText="%textfield.chatMessagePrompt" maxHeight="${dpi.value*40}" minHeight="${dpi.value*40}" prefHeight="${dpi.value*40}" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" />
</children>
</AnchorPane>
</children>
</AnchorPane>
Scaling.java
package securechat.helpers;
import java.awt.GraphicsEnvironment;
/**
*
*/
public final class Scaling {
public double value;
public Scaling() {
value = getDefaultScaling();
}
private double getDefaultScaling() {
int width = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().width;
return width / 1920;
}
public double getValue() {
return value;
}
}
Upvotes: 4
Views: 2301
Reputation: 4067
The main info as you point it is that it is not possible to use an expression in a static attribute and never will BUT you can use a variable.
An expression is written with ${} and must be bound to a property, a "variable" is written with only $var and is accessed through a getter.
I ended with this horrible workaround : AnchorPane.leftAnchor="$dpi.value200"
And implements as many methods as you need in Scaling class:
public final class Scaling {
public double value;
public Scaling() {
value = getDefaultScaling();
}
private double getDefaultScaling() {
int width = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().width;
return width / 1920;
}
public double getValue() {
return value;
}
public double getValue100() {
return value*100;
}
public double getValue200() {
return value*200;
}
public double getValue300() {
return value*300;
}
/* etc.*/
}
Additionnaly, there is a kind of bug in a variable analysis, don't call it "200Value" (or "200dp" as i did). it will stop evaluation if the variable begins with numeric characters.
Upvotes: 1
Reputation: 45456
After some debugging, I've found that in the FXMLLoader
class there are several LinkedList<Attribute>
lists. One if for instance property attributes, like prefHeight="400.0"
, and one is for static property attributes, like AnchorPane.bottomAnchor="0.0"
.
If you use a regular value like prefHeight="400.0"
or a binding expression, like prefWidth="${dpi.value*200}"
, these cases will be processed in this method processPropertyAttribute()
.
For bindings, there are three preliminary checks:
@SuppressWarnings("unchecked")
public void processPropertyAttribute(Attribute attribute) throws IOException {
String value = attribute.value;
if (isBindingExpression(value)) {
// Resolve the expression
Expression expression;
if (attribute.sourceType != null) {
throw constructLoadException("Cannot bind to static property.");
}
if (!isTyped()) {
throw constructLoadException("Cannot bind to untyped object.");
}
if (this.value instanceof Builder) {
throw constructLoadException("Cannot bind to builder property.");
}
// Evaluate the expression
...
} else {
processValue(attribute.sourceType, attribute.name, value);
}
While prefWidth="${dpi.value*200}"
passes these checks, on the contrary, on AnchorPane.leftAnchor="${dpi.value*200}"
you will find that it has a sourceType:
attribute.sourceType = (java.lang.Class) class javafx.scene.layout.AnchorPane
so by design this will happen:
if (attribute.sourceType != null) {
throw constructLoadException("Cannot bind to static property.");
}
Also it is not possible bidirectional binding:
if (isBidirectionalBindingExpression(value)) {
throw constructLoadException(new UnsupportedOperationException("This feature is not currently enabled."));
}
If you look at the docs:
Attributes representing static properties are handled similarly to static property elements and use a similar syntax. In addition to being more concise, static property attributes, like instance property attributes, support location, resource, and variable resolution operators, the only limitation being that it is not possible to create an expression binding to a static property.
You will need to find another way...
EDIT
Based on the FXML provided, my suggestion for a way to avoid static properties will be using HBox
and VBox
, with the desired min/pref/max sizes via bindings, wrapping the controls.
Something like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import securechat.helpers.Scaling?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="securechat.controllers.MainController">
<children>
<fx:define>
<Scaling fx:id="dpi"></Scaling>
</fx:define>
<HBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<VBox fx:id="chatsVBox" maxWidth="${dpi.value*200}" minWidth="${dpi.value*200}" prefWidth="${dpi.value*200}" HBox.hgrow="NEVER">
<children>
<Button fx:id="openNewChatWindowButton" onAction="#openNewChatDialog" text="%button.openNewChatDialog" maxHeight="${dpi.value*40}" minHeight="${dpi.value*40}" prefHeight="${dpi.value*40}" prefWidth="${dpi.value*200}" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" VBox.vgrow="NEVER" />
<ListView fx:id="chatsListView" VBox.vgrow="ALWAYS" />
</children>
</VBox>
<VBox fx:id="chatVBox" HBox.hgrow="ALWAYS">
<children>
<ListView fx:id="chatMessagesListView" VBox.vgrow="ALWAYS" />
<TextField fx:id="chatMessageTextField" onAction="#sendChatMessage" maxHeight="${dpi.value*40}" minHeight="${dpi.value*40}" prefHeight="${dpi.value*40}" promptText="%textfield.chatMessagePrompt" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" VBox.vgrow="NEVER" />
</children>
</VBox>
</children>
</HBox>
</children>
</AnchorPane>
Upvotes: 3