JavaFX fxml Datei eines anderen Packages verwenden
In diesem Artikel zeige ich euch wie man eine JavaFX fxml Datei eines anderen Packages verwenden kann. Das ist recht einfach möglich und erlaubt dadurch eine bessere Gliederung des Java und JavaFX Source Codes auf mehrere Packages. Gerade bei größeren Projekten ist das sehr hilfreich.
JavaFX fxml Datei eines anderen Packages verwenden
Seit ein paar Wochen arbeite ich mich intensiv in Java und speziell mit JavaFX ein. Dabei musste ich feststellen, dass JavaFX im Gegensatz zu AWT und SWING sehr modern wirkt und der Bau von User Interfaces damit viel Spaß macht. Der moderne Ansatz von getrenntem Code und UI (naja, so modern auch wieder nicht) macht die Arbeit viel einfacher. Der Controller wird einmal fix geplant und implementiert, die UI meist später dann noch stark angepasst und verändert.
fxml Datei verwenden
Üblicherweise holen wir und uns den Parent für die Scene mit dem FXMLLoader. Dieser benötigt einen InputStream einer fxml Datei. In dieser speziellen XML Datei ist das Layout für das Fenster beziehungsweise für den Bereich definiert.
@Override public void start(Stage stage) throws Exception{ //create base Scene for complete App BorderPane root = FXMLLoader.load(getClass().getResource("main.fxml")); stage.setTitle("Client"); stage.setScene( new Scene(root, 300, 275) ); root.setCenter(new views.LoginController()); stage.show(); }
Das Ergebnis des Codes ist die im Fenster gerenderte Scene. Diese ist durch die main.fxml Datei definiert (alle Objekte und deren visuelle Darstellung). In meinem Fall sieht das für mein Testprojekt wie folgt aus:
Eingabefelder für Benutzername und Passwort, einen Login Button und noch eine TextArea für einen Log. Im späteren Programm soll das aber nicht das einzige Fenster und schon gar nicht die einzige Stage sein. Eventuell werden mehrere hundert fxml Dateien benötigt. Dazu legt man diese idealerweise in Pakete. Macht man das und erstellt das Programm neu bekommt man folgenden Fehler:
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:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:473) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:372) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:945) Caused by: java.lang.RuntimeException: Exception in Application start method at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:973) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:198) at java.base/java.lang.Thread.run(Thread.java:844) Caused by: java.lang.NullPointerException: Location is required. at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3246) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3210) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3179) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3152) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3129) at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:3122) at sample.Main.start(Main.java:14) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:919) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$11(PlatformImpl.java:449) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418) at java.base/java.security.AccessController.doPrivileged(Native Method) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417) at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96) at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method) at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:277) ... 1 more Exception running application sample.Main
Der wichtige Teil ist die NullPointerException aus sample.Main. Die FXMLLoader.load Methode findet offenbar etwas nicht, es wird ein Null Objekt geliefert anstatt einer Objektinstanz. In unserem nicht ganz optimal gestalteten Code verwenden wir ohne Prüfung die Rückgabe von:
getClass().getResource("main.fxml")
Dort wird die main.fxml Datei nicht gefunden und schlicht und einfach null zurückgegeben. Wie kann man nun aber eine JavaFX fxml Datei eines anderen Packages verwenden?
Lösung
Das funktioniert unter Angabe des genauen Pfads….und dazu gehört auch der Paketname. In meinem Fall liegen alle fxml Dateien und zugehörige Controller Klassen im views Package, weshalb der korrekte Code wie folgt lautet:
FXMLLoader.load(getClass().getResource("/views/main.fxml"));
Fazit
Ich habe euch gezeigt wie man zur besseren Gliederung des Source Codes fxml Dateien in Pakete auslagern kann. Wenn man diese nun für eine Scene verwenden möchte, dann muss man den korrekten Dateinamen (mit Paketnamen) angeben. Tut man das nicht, dann wird im schlimmsten Fall eine NullPointerException geworfen und das Programm endet.
Danke für die gut verständliche Erklärung.
Liebe Grüße
gerne 😉
Oh gott. Den ganzen tag habe ich nach dieser Antwort gesucht XD
DANKE.
Schluprojekt für in 3 Tage wurde gerettet xD