2017-05-16 3 views
0

Ich weiß, dass diese Frage viele Male gestellt wird, aber ich kann keine Lösung finden, die für mich funktioniert (eigentlich kann ich nicht einmal sehen, was ich falsch mache).Laden mehrerer FXML-Dateien schlägt fehl, wenn Controller definiert sind

Die Grundidee besteht darin, bei Bedarf GUI-Komponenten zu laden. Also habe ich die GUI in verschiedene FXML-Dateien strukturiert und Controller-Klassen implementiert. Beide - FXML-Dateien und Klassen - sind im selben Paket gespeichert, aber es ist ein Paket für jede Komponente. Jede FXML-Datei wird geladen und der GUI hinzugefügt, solange ich die Controller-Klasse nicht in der FXML-Datei (fx: controller) definiere. Wenn es definiert ist, bekomme ich eine LoadException.

Zum besseren Verständnis hier ist mein Code (vereinfacht):

Main.java:

package application; 

import application.a.ControllerA; 
import javafx.application.Application; 
import javafx.event.ActionEvent; 
import javafx.fxml.FXML; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.layout.BorderPane; 
import javafx.stage.Stage; 

public class Main extends Application 
{ 
    // Button aus MainLayout.fxml 
    @FXML 
    private Button button; 

    @Override 
    public void start(Stage primaryStage) 
    { 
     try 
     { 
      BorderPane root = new BorderPane(); 

      Parent contentMain = FXMLLoader.load(getClass().getResource("MainLayout.fxml")); 
      ControllerA contentA = new ControllerA(root); 

      root.setTop(contentA.getContent()); 
      root.setCenter(contentMain); 

      Scene scene = new Scene(root, 400, 400); 
      scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); 
      primaryStage.setScene(scene); 
      primaryStage.show(); 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 
    } 

    public static void main(String[] args) 
    { 
     launch(args); 
    } 

    // Event-Handler für den Button -> funktioniert! 
    @FXML 
    public void buttonClicked(ActionEvent e) 
    { 
     if (!button.getText().equals("NEW")) 
     { 
      button.setText("NEW"); 
     } 
     else 
     { 
      button.setText("OLD"); 
     } 
    } 
} 

Diese Klasse ist auch eine Steuerung für das folgende Layout (und es funktioniert Flossen bisher):

MainLayout.fxml:

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.scene.control.*?> 
<?import java.lang.*?> 
<?import javafx.scene.layout.*?> 
<?import javafx.scene.layout.GridPane?> 

<Pane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Main"> 
    <children> 
     <Button fx:id="button" mnemonicParsing="false" onAction="#buttonClicked" text="Button" /> 
    </children> 
</Pane> 

In einem Teilpaket (genannt a) von 0.123.Sie diese finden:

ControllerA.java:

package application.a; 

import java.net.URL; 

import javafx.event.ActionEvent; 
import javafx.fxml.FXML; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.control.Button; 
import javafx.scene.layout.BorderPane; 

public class ControllerA 
{ 
    private Parent content; 

    @FXML 
    private Button buttonA; 

    public ControllerA(BorderPane root) 
    { 
     String sceneFile = "A.fxml"; 
     URL url = null; 
     try 
     { 
      url = getClass().getResource(sceneFile); 
      content = FXMLLoader.load(url); 
     } 
     catch (Exception ex) 
     { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
     } 
    } 

    public Parent getContent() 
    { 
     return content; 
    } 

    @FXML 
    public void clickedA(ActionEvent e) 
    { 
     buttonA.setText("Clicked already"); 
    } 
} 

A.fxml:

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.scene.control.*?> 
<?import java.lang.*?> 
<?import javafx.scene.layout.*?> 
<?import javafx.scene.layout.GridPane?> 

<Pane 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" fx:controller="application.a.ControllerA"> 
    <children> 
     <Button fx:id="buttonA" layoutX="274.0" layoutY="188.0" mnemonicParsing="false" onAction="#clickedA" text="A" /> 
    </children> 
</Pane> 

Und das ist, wo alles schief gelaufen ist:

javafx.fxml.LoadException: 
/Z:/BachelorArbeit/Projektdateien/Entwicklung/EclipseWorkspace/Sandbox/bin/application/a/A.fxml:8 

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601) 
    at javafx.fxml.FXMLLoader.access$700(FXMLLoader.java:103) 
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:932) 
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971) 
    at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220) 
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744) 
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124) 
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104) 
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097) 
    at application.a.ControllerA.<init>(ControllerA.java:26) 
    at application.Main.start(Main.java:35) 
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863) 
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326) 
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294) 
    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$148(WinApplication.java:191) 
    at java.lang.Thread.run(Thread.java:745) 
Caused by: java.lang.InstantiationException: application.a.ControllerA 
    at java.lang.Class.newInstance(Class.java:427) 
    at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51) 
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927) 
    ... 23 more 
Caused by: java.lang.NoSuchMethodException: application.a.ControllerA.<init>() 
    at java.lang.Class.getConstructor0(Class.java:3082) 
    at java.lang.Class.newInstance(Class.java:412) 
    ... 25 more 

I habe versucht mit dem Pfad-String herumzubasteln wie

  • "./a/A.fxml"
  • "/application/a/A.fxml"
  • "A.fxml"
  • "a/A.fxml"
  • ...

aber nichts hat funktioniert. Ich wäre sehr erleichtert, wenn mich jemand mit diesem Problem hinhalten könnte.

+0

Was ist Zeile 35 in 'Main.java'? –

+0

Ups, Tut mir leid: 'ControllerA contentA = neuer ControllerA (root);' – Marco

+0

und Zeile 26 in 'ControllerA.java'? –

Antwort

0

Sie mischen zwei verschiedene Arten der Verwendung von FXML und Controllern. Wenn Ihre FXML-Datei fx:controller="SomeClass" hat, dann wird die FXMLLoader diese Klasse instanziieren und die Instanz als Controller verwenden: Mit anderen Worten, die FXML erstellt den Controller.

Auf der anderen Seite lädt der Konstruktor Ihres Controllers die FXML, so dass Sie auch den Controller haben, der die in der FXML definierte Benutzeroberfläche erstellt.

Der Grund für die Ausnahme ist, dass, wenn die FXMLLoader das fx:controller Attribut trifft, ruft es das Konstruktor ohne Argumente der angegebenen Klasse: das heißt in diesem Fall versucht es new ControllerA() zu nennen. Da es keinen solchen Konstruktor erhalten Sie die Ausnahme:

java.lang.NoSuchMethodException: application.a.ControllerA.<init>() 

Es ist nicht wirklich klar, was der Zweck des BorderPane Parameter an den Konstruktor der Steuerung ist, wie Sie es nie verwenden. Aber selbst wenn Sie hier einen geeigneten Konstruktor hätten, würde dies ein anderes Problem verursachen: Das Laden der FXML würde den Konstruktor des Controllers aufrufen, der die FXML lädt, wodurch der Konstruktor des Controllers usw. aufgerufen würde: Sie erhalten eine StackOverflowException.

  1. entfernen Sie das fx:controller Attribut aus der FXML Datei
  2. Explizit stellen Sie den Regler auf der FXMLLoader dem aktuellen Controller-Instanz: Wenn Sie die FXML von der Steuerung Konstruktor laden möchten

    public ControllerA(BorderPane root) 
    { 
        String sceneFile = "A.fxml"; 
        URL url = null; 
        try 
        { 
         url = getClass().getResource(sceneFile); 
         FXMLLoader loader = new FXMLLoader(url); 
         loader.setController(this); 
         content = loader.load(); 
        } 
        catch (Exception ex) 
        { 
          // TODO Auto-generated catch block 
          e.printStackTrace(); 
        } 
    } 
    
+0

Dies ist die Lösung, die ich gesucht habe! Genau diesen Punkt schien mir verwirrt zu sein. Vielen Dank. – Marco

0

Ich zeige Ihnen ein minimal funktionierendes Beispielprojekt und schlage vor, dass Sie Ihre Struktur daran anpassen.

Die Ordnerstruktur ist wie folgt

enter image description here

Hier ist die Hauptklasse Paket dynamic.content.javafx;

import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.layout.GridPane; 
import javafx.stage.Stage; 

public final class Main extends Application { 
    @Override 
    public void start(Stage stage) throws Exception { 
     Parent root = FXMLLoader.load(getClass().getResource("main.fxml")); 
     Scene scene = new Scene(root, 300, 200); 

     stage.setTitle("FXML Welcome"); 
     stage.setScene(scene); 
     stage.show(); 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 

Der Hauptcontroller

package dynamic.content.javafx; 

import java.io.IOException; 
import java.net.URL; 
import java.util.ResourceBundle; 

import javafx.fxml.FXML; 
import javafx.fxml.FXMLLoader; 
import javafx.fxml.Initializable; 
import javafx.scene.Parent; 
import javafx.scene.control.Button; 
import javafx.scene.layout.AnchorPane; 

public final class MainController implements Initializable{ 

    @FXML 
    private Button btnSwitch; 

    @FXML 
    private AnchorPane contentPane; 

    @Override 
    public void initialize(URL location, ResourceBundle resources) { 
     // TODO Auto-generated method stub 
    } 

    private boolean swtch = false; 
    @FXML 
    public void handleContentSwitch() throws IOException{ 
     Parent contentNode = null; 

     if(swtch){ 
      System.out.println("loading content A"); 
      contentNode = FXMLLoader.load(getClass().getResource("./content/content_a.fxml"));   
     }else{ 
      System.out.println("loading content B"); 
      contentNode = FXMLLoader.load(getClass().getResource("./content/content_b.fxml")); 
     } 
     contentPane.getChildren().clear(); 
     contentPane.getChildren().add(contentNode); 

     swtch = !swtch; 
    } 

} 

Der Haupt FXML

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.scene.control.*?> 
<?import java.lang.*?> 
<?import javafx.scene.layout.*?> 


<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.MainController"> 
    <children> 
     <Button fx:id="btnSwitch" onAction="#handleContentSwitch" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" text="Switch Content" /> 
     <AnchorPane fx:id="contentPane" prefHeight="150.0" prefWidth="300.0" /> 
    </children> 
</VBox> 

Lasst uns sagen, dass wir Inhalte A und B haben wir

einen Controller und ein FXML jeder von ihnen erstellen müssen
package dynamic.content.javafx.content; 

import java.net.URL; 
import java.util.ResourceBundle; 

import javafx.fxml.Initializable; 
import javafx.fxml.FXML; 
import javafx.scene.control.Label; 

public class ContentAController implements Initializable{ 

    @FXML 
    private Label labl; 

    @Override 
    public void initialize(URL location, ResourceBundle resources) { 
     labl.setText("Content A"); 
    } 

} 

Und jetzt die entsprechenden FXML

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.scene.control.*?> 
<?import java.lang.*?> 
<?import javafx.scene.layout.*?> 

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.content.ContentAController"> 
    <children> 
     <Label fx:id="labl" alignment="CENTER" contentDisplay="CENTER" text="Label" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" /> 
    </children> 
</AnchorPane> 

Hier folgt der zweite Controller

package dynamic.content.javafx.content; 

import java.net.URL; 
import java.util.ResourceBundle; 

import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.Label; 

public class ContentBController implements Initializable{ 

    @FXML 
    private Label labl; 

    @Override 
    public void initialize(URL location, ResourceBundle resources) { 
     labl.setText("Content B"); 
    } 

} 

und die zweite FXML

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.scene.control.*?> 
<?import java.lang.*?> 
<?import javafx.scene.layout.*?> 


<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.content.ContentBController"> 
    <children> 
     <Label fx:id="labl" alignment="CENTER" contentDisplay="CENTER" text="Label" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" /> 
    </children> 
</AnchorPane> 

Wenn Sie die Taste drücken, kennen den Inhalt dynamisch

geladen wird, wenn Sie haben Fragen, lassen Sie es mich wissen :-)

+0

Für dieses minimale Arbeitsbeispiel funktioniert es auch gut für mich. Aber was ich tun möchte, ist das Laden der GUI-Elemente nur bei Bedarf. Soweit ich weiß, gibt es keine andere Möglichkeit, diese Dateien separat zu laden. Anders gesagt: Wenn ich eine HBox (mit etwas Inhalt, der von einer Controller-Klasse gesteuert wird) in die VBox laden möchte (in Ihrem Beispiel), wie mache ich das, wenn beide (die VBox und die HBox) getrennt sind FXML-Dateien)? Was ich getan habe, arbeitet für die Main-Klasse, aber warum funktioniert es nicht für ControllerA und A.fxml? – Marco

+0

... auch wenn ich sie in das gleiche Paket wie Main/MainLayout.fxml gebe und nur den fx: controller in A.fxml korrigiere – Marco

+0

Ich habe meine Antwort auf dein Problem zugeschnitten :-) – Westranger

Verwandte Themen