I'm trying to use the resource
tag inside fx:include
in a FXML file.
What I don't understand is, that loading the resource bundle manually with ResourceBundle.getBundle()
works completely fine. I already tried a lot of variants in the fxml like:
"lang_challenges"
"wand555/github/io/challengesreworkedgui/lang_challenges"
package wand555.github.io.challengesreworkedgui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
public class ChallengeApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
ResourceBundle.clearCache();
ResourceBundle bundle = new ResourceBundleWrapper(ResourceBundle.getBundle("wand555/github/io/challengesreworkedgui/lang_challenges"));
System.out.println(bundle.getString("challenge.name")); // outputs "abc"
FXMLLoader loader = new FXMLLoader(ChallengeApplication.class.getResource("overview.fxml"), bundle);
Parent root = loader.load();
Scene scene = new Scene(root, 1000, 1000);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
// this class effectively does nothing, but it will be loaded by the
// application class loader
// instead of the system class loader.
private static class ResourceBundleWrapper extends ResourceBundle {
private final ResourceBundle bundle;
ResourceBundleWrapper(ResourceBundle bundle) {
this.bundle = bundle;
}
@Override
protected Object handleGetObject(String key) {
return bundle.getObject(key);
}
@Override
public Enumeration<String> getKeys() {
return bundle.getKeys();
}
@Override
public boolean containsKey(String key) {
return bundle.containsKey(key);
}
@Override
public Locale getLocale() {
return bundle.getLocale();
}
@Override
public Set<String> keySet() {
return bundle.keySet();
}
}
}
overview.fxml
:
<?xml version="1.0" encoding="UTF-8"?>
<AnchorPane prefHeight="500.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="wand555.github.io.challengesreworkedgui.controllers.OverviewController">
<children>
<VBox>
<children>
<StackPane alignment="CENTER_LEFT">
<children>
<Button fx:id="exportButton" mnemonicParsing="false" onAction="#onExport" text="Export" />
</children>
</StackPane>
<HBox prefHeight="100.0" prefWidth="200.0" spacing="25.0">
<children>
<fx:include fx:id="challengesOverview" source="challenges/challenges_overview.fxml" resources="lang_challenges" charset="utf-8"/>
<Separator orientation="VERTICAL" prefHeight="200.0" />
<fx:include source="goals/goal_overview.fxml" />
<Separator orientation="VERTICAL" prefHeight="200.0" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
The lang_challenges.properties
contains a single key-value pair
challenge.name=abc
The `lang_challenges_de.properties' has the same content
challenge.name=abc
And this is the error message I'm getting
Exception in Application start method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at javafx.graphics@19/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:465)
at javafx.graphics@19/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1082)
Caused by: java.lang.RuntimeException: Exception in Application start method
at javafx.graphics@19/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901)
at javafx.graphics@19/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: javafx.fxml.LoadException:
/Users/felixnaumann/Documents/ChallengesReworked/ChallengesReworkedGUI/target/classes/wand555/github/io/challengesreworkedgui/overview.fxml:21
at javafx.fxml@19/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2707)
at javafx.fxml@19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2685)
at javafx.fxml@19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
at javafx.fxml@19/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2516)
at challenges.reworked.gui/wand555.github.io.challengesreworkedgui.ChallengeApplication.start(ChallengeApplication.java:22)
at javafx.graphics@19/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at javafx.graphics@19/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at javafx.graphics@19/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics@19/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at javafx.graphics@19/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
Caused by: java.util.MissingResourceException: Can't find bundle for base name lang_challenges, locale de_DE
at java.base/java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:2045)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1683)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1575)
at java.base/java.util.ResourceBundle.getBundle(ResourceBundle.java:1280)
at javafx.fxml@19/javafx.fxml.FXMLLoader$IncludeElement.processAttribute(FXMLLoader.java:1100)
at javafx.fxml@19/javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:230)
at javafx.fxml@19/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:755)
at javafx.fxml@19/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2808)
at javafx.fxml@19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2634)
... 9 more
Exception running application wand555.github.io.challengesreworkedgui.ChallengeApplication
Whew, finally figured it out after digging deep into the source code of ResourceBundle
and ClassLoader
.
Put your .properties
files at the root of the resources
folder. Then use it inside fxml
<fx:include source="second.fxml" resources="second_bundle"/>
and then everything should work fine. However this approach is not suitable for larger projects which subdivide the resource bundles into different packages.
Give the fully qualified path name in the resources
tag. So for example if your package structure from src
is com/example/demo/
( also in the resources folder ) then use
<fx:include source="second.fxml" resources="com/example/demo/second_bundle"/>
But we are not done yet. You need to open the package to all modules in the module-info.java
, explicitly using
opens com.example.demo to javafx.fxml;
does not work. Instead you need to write
opens com.example.demo;
Honestly to me this sounds like a bug. I debugged the entire loading process and when the second_bundle
is loaded from inside the fxml file, the caller module is javafx.fxml
. And as the java doc for getResourceAsStream (which is internally called when loading) states:
A package name is derived from the resource name. If the package name is a package in the module then the resource can only be located by the caller of this method when the package is open to at least the caller's module. If the resource is not in a package in the module then the resource is not encapsulated.
So in theory opening your package to just javafx.fxml
should work, but it doesn't...
This demo project uses sub-packages.
Project structure:
HelloApplication
:
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
ResourceBundle bundle = new ResourceBundleWrapper(ResourceBundle.getBundle("test_bundle"));
System.out.println(bundle.getString("first.button")); // outputs "English/Deutsch"
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("first.fxml"), bundle);
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
// this class effectively does nothing, but it will be loaded by the
// application class loader
// instead of the system class loader.
private static class ResourceBundleWrapper extends ResourceBundle {
private final ResourceBundle bundle;
ResourceBundleWrapper(ResourceBundle bundle) {
this.bundle = bundle;
}
@Override
protected Object handleGetObject(String key) {
return bundle.getObject(key);
}
@Override
public Enumeration<String> getKeys() {
return bundle.getKeys();
}
@Override
public boolean containsKey(String key) {
return bundle.containsKey(key);
}
@Override
public Locale getLocale() {
return bundle.getLocale();
}
@Override
public Set<String> keySet() {
return bundle.keySet();
}
}
}
first.fxml
:
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.demo.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="%first.button" onAction="#onHelloButtonClick"/>
<fx:include source="second.fxml" resources="com/example/demo/second_bundle"/>
</VBox>
second.fxml
:
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="%second.button"/>
</VBox>
test_bundle.properties
:
first.button=English
second_bundle.properties
:
second.button=Hello
module-info.java
:
module com.example.demo {
requires javafx.controls;
requires javafx.fxml;
opens com.example.demo;
exports com.example.demo;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.