JavaFX Daten zwischen Controller "austauschen"

ralfb1105

Bekanntes Mitglied
Hallo zusammen,

ich habe mal wieder eine Frage zu JavaFX und dem Austausch, sprich das auslesen von z.B. Text Feldern zwischen zwei Controller.
Ich habe dazu folgenden Thread von Euch gelesen und es versucht umzusetzen, es endet aber leider immer in einer NPE und ich weiß nicht genau wie/wo ich die " Controller Injection" machen soll.
https://www.java-forum.org/thema/javafx-in-mehrere-controller-aufteilen.180123/

Hier mal als Referenz mein einfaches kleines Beispiel:
Ich starte ein Fenster "Controller1" welches ein TextField und einen Button enthält. Bei drücken des Button wird ein weiteres Fenster -> Controller2, geöffnet. Dieses hat auch wieder ein TextField und ein Button. Beim drücken des Button in Fenster2 (Controller2) soll der Inhalt von controller1.textfield in das TextField von Controller2 geschrieben werden.

Referenz Code:
1. Main.java
Java:
package application;
   
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Box1.fxml"));
            Parent root = loader.load();
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Controller1");
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
   
    public static void main(String[] args) {
        launch(args);
    }
}
2. Controller1.java
Java:
package application;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class Controller1 {
   
    Controller2 controller2;
   
    @FXML
    TextField textField1;
   
    @FXML
    Button startCtrl2;
   
   
   
    public void initialize() {
        textField1.setText("The quick brown fox jumps over the lazy dog");
    }
   
    public void startCtrl2() throws IOException  {
        FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        Stage stageBox2 = new Stage();
        stageBox2.initModality(Modality.APPLICATION_MODAL);
        stageBox2.setOpacity(1);
        stageBox2.setTitle("Controller2");
        Scene sceneBox2= new Scene(rootBox2);
        sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stageBox2.setScene(sceneBox2);
        stageBox2.show();
    }

}
3. Controller2.java
Java:
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;

public class Controller2 {
   
    Controller1 controller1;
   
    @FXML
    TextField textField2;
   
    @FXML
    Button setLabel;
   
    public void initialize() {
       
    }
   

    @FXML
    public void setInfoTextField2() {
        textField2.setText(controller1.textField1.getText());
    }

}
4. Box1.fxml
Java:
<?xml version="1.0" encoding="UTF-8"?>

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

<VBox prefHeight="151.0" prefWidth="429.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller1">
   <children>
      <HBox alignment="CENTER" prefHeight="40.0" prefWidth="337.0">
         <children>
            <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Controller1">
               <font>
                  <Font name="System Bold" size="18.0" />
               </font>
            </Text>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </VBox.margin>
      </HBox>
      <HBox alignment="CENTER_LEFT" prefHeight="72.0" prefWidth="337.0">
         <children>
            <TextField fx:id="textField1" editable="false" prefHeight="30.0" prefWidth="272.0" />
            <Button fx:id="startCtrl2" mnemonicParsing="false" onAction="#startCtrl2" text="Start Controller2">
               <HBox.margin>
                  <Insets bottom="10.0" left="30.0" top="10.0" />
               </HBox.margin>
            </Button>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </VBox.margin>
      </HBox>
   </children>
</VBox>
5. Box2.fxml
Java:
<?xml version="1.0" encoding="UTF-8"?>

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

<VBox prefHeight="173.0" prefWidth="320.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller2">
   <children>
      <HBox alignment="CENTER" prefHeight="55.0" prefWidth="320.0">
         <children>
            <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Controller2">
               <font>
                  <Font size="18.0" />
               </font>
            </Text>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </VBox.margin>
      </HBox>
      <HBox alignment="CENTER_LEFT" prefHeight="62.0" prefWidth="320.0">
         <children>
            <TextField fx:id="textField2" editable="false" prefHeight="25.0" prefWidth="210.0" />
            <Button fx:id="setLabel" mnemonicParsing="false" onAction="#setInfoTextField2" text="Set Label">
               <HBox.margin>
                  <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
               </HBox.margin>
            </Button>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </VBox.margin>
      </HBox>
   </children>
</VBox>

ich habe schon versucht die Deklaration "Controller2 controller2;" in die initialize() Methode zu schreiben, bringt aber auch nichts.
Die NPE kommt in der Zeile
Java:
textField2.setText(controller1.textField1.getText());
also wenn ich auf das Objekt zugreifen will.

Was ich in dem o.g. Thread nicht ganz verstanden habe ... muss ich in den .fxml Dateien noch includes einbauen? Wenn ja, wo oder geht das auch über den SceneBuilder?

Gruß

Ralf
 

mrBrown

Super-Moderator
Mitarbeiter
Wenn du per Hand neue FXML lädst und anzeigst, musst du die selbst um die Controller kümmern ;)

In diesem Fall: in startCtrl2 den Controller2 aus dem Loader holen und dann dem Controller2 den Controller1 übergeben.


Das automatische Setzen der Controller klappt nur, wenn die Views direkt in der FXML eingebunden sind, aber auch da wird nur der Controller im Parent gesetzt, um den im Kind muss man sich selber kümmern.
 

ralfb1105

Bekanntes Mitglied
Hallo mrBrown,
sorry, aber ich muss mal wieder im Detail nachfragen weil ich es noch nicht ganz verstanden habe.

in startCtrl2 den Controller2 aus dem Loader holen und dann dem Controller2 den Controller1 übergeben.

Ich war der Meinung das ich mit der Zeile
Java:
FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
die Objekte aus "Box2.fxml" hole.
Wie muss ich den jetzt den Controller2 aus dem Loader holen und dem Controller1 übergeben?

Ich habe irgendwie jetzt einen Knoten im Kopf ... ohne zu wissen wie es zu codieren ist, hätte ich jetzt gesagt das ich in startCtrl2 den Controller1 holen muss und diesen daann dem Controller2 übergeben muss damit der dann auf die Objekte von Controller1 zugreifen kann ... ???

Könntest Du mir an Hand meines Beispiels ein Code Schnipsel zeigen bezogen auf Deine obige Aussage?

Gruß
Ralf
 

mrBrown

Super-Moderator
Mitarbeiter
die Objekte aus "Box2.fxml" hole.
Das lädt die FXML, über den FXMLLoader kannst du dann Parent (=die View) und den Controller bekommen.
Wie muss ich den jetzt den Controller2 aus dem Loader holen und dem Controller1 übergeben?
loaderBox2.getController() zum bekommen von Controller2 und in Controller2 ein Setter für Controller1

Ich habe irgendwie jetzt einen Knoten im Kopf ... ohne zu wissen wie es zu codieren ist, hätte ich jetzt gesagt das ich in startCtrl2 den Controller1 holen muss und diesen daann dem Controller2 übergeben muss damit der dann auf die Objekte von Controller1 zugreifen kann ... ???
Ja, in startCtrl2 bist du allerdings schon Controller1 ("this"), den musst du also nicht erst holen, der Rest passt aber.
 

ralfb1105

Bekanntes Mitglied
Ich bin zu blöd :confused:
Sorry ... ich habe e simmer noch nicht ...
Was ich bis jetzt verstanden habe ..
Ich habe nun im Controller2 einen Setter für Controller1 eingebaut. Hier der Controller2.java:
Java:
package application;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class Controller2 {

    @FXML
    Controller1 controller1;

    public void setController1(Controller1 controller1) {
        this.controller1 = controller1;
    }

    @FXML
    VBox box2;

    @FXML
    TextField textField2;

    @FXML
    Button setLabel;

    public void initialize() {

    }

    @FXML
    public void setInfoTextField2() throws IOException {
        textField2.setText(controller1.getTextField1Data());
    }

}
Wo und wie in Controller1 muss ich denn jetzt den Setter "setController1" von Controller2.java aufrufen?
Ich vermute doch, so wie ich Dich verstanden habe in "startCtrl2()" richtig?

Dort habe ich doch aber mit "loaderbox2" den Controller2 geholt - oder liege ich hier falsch?

Könntest Du mir noch bei der letzten Anweisung, sprich wie muss ich die Setter Methode in startCtrl2() aufrufen helfen?
DANKE!
Gruß
Ralf
 

mrBrown

Super-Moderator
Mitarbeiter
Wo und wie in Controller1 muss ich denn jetzt den Setter "setController1" von Controller2.java aufrufen?
Ich vermute doch, so wie ich Dich verstanden habe in "startCtrl2()" richtig?
Ja.

Dort habe ich doch aber mit "loaderbox2" den Controller2 geholt - oder liege ich hier falsch?
Nein, loaderbox2 ist ein FXMLLoader, aus diesem kannst du aber den Controller erhalten

Könntest Du mir noch bei der letzten Anweisung, sprich wie muss ich die Setter Methode in startCtrl2() aufrufen helfen?
Java:
        FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        //folgende zwei Zeilen kommen dazu:
        this.controller2=loaderBox2.getController();  //controller aus dem loader bekommen
        this.controller2.setController1(this);  //setter auf dem Controller aufrufen, um controller1 dort zu setzen
        ...
 

ralfb1105

Bekanntes Mitglied
Hier noch einmal der komplette Code als Referenz, falls es mal jemand gebrauchen kann:
Main.java
Java:
package application;

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Box1.fxml"));
            Parent root = loader.load();
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Controller1");
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Controller1.java
Java:
package application;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class Controller1 {

    @FXML
    Controller2 controller2;

    @FXML
    VBox box1;

    @FXML
    private TextField textField1;

    @FXML
    Button startCtrl2;

    public void initialize() {
        textField1.setText("The quick brown fox jumps over the lazy dog");
    }

    public void startCtrl2() throws IOException {
        FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        this.controller2 = loaderBox2.getController(); // controller aus dem Loader bekommen
        this.controller2.setController1(this); // Setter auf dem Controller (controller2) aufrufen, um controller1 dort
                                                // zu setzen
        Stage stageBox2 = new Stage();
        stageBox2.initModality(Modality.APPLICATION_MODAL);
        stageBox2.setOpacity(1);
        stageBox2.setTitle("Controller2");
        Scene sceneBox2 = new Scene(rootBox2);
        sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stageBox2.setScene(sceneBox2);
        stageBox2.show();
    }

    public String getTextField1Data() {
        return textField1.getText();
    }

}
Controller2.java
Java:
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class Controller2 {

    @FXML
    Controller1 controller1;

    public void setController1(Controller1 controller1) {
        this.controller1 = controller1;
    }

    @FXML
    VBox box2;

    @FXML
    TextField textField2;

    @FXML
    Button setLabel;

    public void initialize() {

    }

    @FXML
    public void setInfoTextField2() {
        textField2.setText(controller1.getTextField1Data());
    }

}

Gruß
Ralf
 

dzim

Top Contributor
Nur so als Nachtrag, wenn man ein FXML in ein anderes Verschachteln will (und nicht mehr austauschen möchte), kann man das auch einfacher haben:
HTML:
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="my.package.Controller1" spacing="10.0">
    <fx:define>
        <fx:include fx:id="fmxl2" source="/ui/MyFxml2.fxml"/>
    </fx:define>

<!-- ........ -->

    <!-- fügt es den Children der umgebenden VBox hinzu (Achtung: kein "$" am Anfang!) -->
    <fx:reference source="fmxl2" />

    <!-- bei Controls, die nur ein Child haben können (Achtung: "$" am Anfang!): -->
    <!-- <ScrollPane content="$fxml2" /> -->
</VBox>

Das MyFxml2.fxml mit seinem Controller (z.B. my.package.Controller2) kannst du im my.package.Controller1 dann einfach per @FXML-Annotation bekommen:

Java:
// Du kannst dir den Root des anderen FXMLs holen, Name = <fx:id vom include-Block>
@FXML private VBox fxml2;
// auch der Controller ist einfach: Name = <fx:id vom include-Block>Controller
@FXML private Controller2 fxml2Controller;
 

ralfb1105

Bekanntes Mitglied
Hallo dzim,

Danke für Deinen Input!! Ich habe das jetzt (noch) nicht im Code getestet, hätte dazu aber eine Frage.
Würde ich mit der von Dir beschriebenen Methode das von mir im Eingang beschriebene "Problem", sprich den Zugriff auf z.B. ein TextField welches in Controller1 - box1.fxml - erstellt/registriert ist, aus Controller2 - box2.fxml - realisieren können?

Gruß
Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo mrBrown, @All,

jetzt habe ich doch noch ein Problem die Lösung von mrBrown in meine Applikation zu implementieren :(

Problem:
Ich möchte in Controller2 das TextField nicht über einen Button setzen, sondern das TextFeld soll gefüllt sein/werden wenn das Fenster aufgebaut wird.

Ich war naiv davon ausgegangen das dies über die initialize() Methode funktionieren sollte
Java:
public void initialize() {
        textField2.setText(controller1.getTextField1Data());
    }
dies führt aber zu einer NPE, da anscheinend zu dem Zeitpunkt das Objekt noch nicht zur Verfügung steht.

Frage:
Wie/Wo müsste ich den Code einfügen damit er das Textfeld beim starten des Fenster sofort setzt?

Gruß
Ralf
 

dzim

Top Contributor
Wenn du es im Controller2 "injectest" - also einen Setter machst, spricht grundsätzlich nichts dagegen.

Aber:
Bedenke, dass das eher unsauber ist. Ich arbeite i.d.R. mit einem Dependency Injection Framework und injecte mir über dieses z.B. ein zentrales App-Model, in dem ich StringProperties, etc., hinterlege. An denen kannst du nicht für Änderungen registieren, oder andere UI-Bereiche sogar davon ändern lassen. Vorteil ist, dass du nicht Teile deines UIs an andere UIs übergibst, sondern schön Abstrakt bleibst.

Injection kann man übrigens auch auf einfache Art über die FXMLLoader machen: Entweder den Controller aus dem Loader holen und dein Model aktiv setzen (Variante1) oder du übernimmst die Controller-Instanziierung für deinen FXMLLoader (Variante2).

  • Variante 1
    • dein Kontroller könnte z.B. ein von dir erstelltes Interface implementieren -> check ob Controller Interface wirklich implementiert -> Cast -> setter nutzen
    • Achtung: dein Setter wird nach der initialize-Methode aufgerufen!
  • Variante 2
    • Beispiel hier: https://github.com/bgmf/poc/blob/ma.../dzim/tests/fx/MainWithControllerFactory.java
    • Wieder wie in Variante 1 verfahren (Interface -> überprüfen, ob Interface verwendet wird -> Cast -> setter)
    • Achtung: dein Setter wird vor der initialize-Methode aufgerufen!
    • PS: Die Variante könnte dein Problem von eben lösen, da du hier vorm initialize schon die Daten da hast und im initialize nur noch setText machen musst...

Zum Model selbst: Wenn du es immer brav überall rein gibst, ist der beste Weg, dein Model-Objekt im Main anzulegen (start-Methode der Applikation-Klasse) und dann zu übergeben...
 

ralfb1105

Bekanntes Mitglied
Hallo dzim,

leider reicht mein Know How für Deine Ausführungen absolut nicht aus um das im Moment zu verstehen, geschweige denn umzusetzen :-(

Ich habe es dann, ohne es im Detail zu verstehen, mal wie in Deinem GitHib Projekt beschrieben implementiert, und es läuft so wie vorher - wenn ich den Button drücke in Controller2 wird der Text aus dem Controller1 in das Textfeld Controller2 geschrieben.

Allerdings kommt die NPE auch hier wenn ich in der initialize() Methode versuche den Text zu setzen ...

Hier mal der Code als Refernz, vielleicht sieht Du ja einen fehler oder kannst mir einen Tipp geben wie ich das lösen könnte:
1. Main.java
Java:
package application;

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        try {
            // FXMLLoader loader = new FXMLLoader(getClass().getResource("Box1.fxml"));
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(this::controllerForClass);
            loader.setLocation(getClass().getResource("Box1.fxml"));
            Parent root = loader.load();
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Controller1");
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
2. Controller1.java
Java:
package application;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class Controller1 {

    @FXML
    private Controller2 controller2;

    @FXML
    VBox box1;

    @FXML
    private TextField textField1;

    @FXML
    Button startCtrl2;

    public void initialize() {
        textField1.setText("The quick brown fox jumps over the lazy dog");
    }

    public void startCtrl2() throws IOException {
        // FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
        FXMLLoader loaderBox2 = new FXMLLoader();
        loaderBox2.setControllerFactory(this::controllerForClass);
        loaderBox2.setLocation(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        this.controller2 = loaderBox2.getController(); // controller aus dem Loader bekommen
        this.controller2.setController1(this); // Setter auf dem Controller (controller2) aufrufen, um controller1 dort
                                                // zu setzen
        Stage stageBox2 = new Stage();
        stageBox2.initModality(Modality.APPLICATION_MODAL);
        stageBox2.setOpacity(1);
        stageBox2.setTitle("Controller2");
        Scene sceneBox2 = new Scene(rootBox2);
        sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stageBox2.setScene(sceneBox2);
        stageBox2.show();
    }

    public String getTextField1Data() {
        return textField1.getText();
    }

    private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

}
3. Controller2.java
Java:
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class Controller2 {

    public Controller2() {
        super();
    }

    @FXML
    private Controller1 controller1;

    public void setController1(Controller1 controller1) {
        this.controller1 = controller1;
    }

    @FXML
    VBox box2;

    @FXML
    TextField textField2;

    @FXML
    Button setLabel;

    public void initialize() {
        setInfoTextField2();
    }

    @FXML
    public void setInfoTextField2() {
        textField2.setText(controller1.getTextField1Data());
    }

}

Gruß
Ralf
 

dzim

Top Contributor
Heute wird's bei mir sicher nichts mehr (es sei denn, ich hab Nachts mal Langeweile...), aber ich kann morgen früh mal versuchen, ein vollständiges Bsp. zusammenzuhacken und in das Github-Repository einpflegen.
 

ralfb1105

Bekanntes Mitglied
Hallo dzim,

ich hoffe für Dich das Du Nachts keine Langeweile hast und Deinen Feierabend genießen kannst :)

Ich bin allerdings auch sehr froh das Du ein Beispiel erstellen willst, denn es hat mich schon der Ehrgeiz gepackt die Applikation so zu erstellen wie es am Besten siot, und Dein Ansatz klingt sehr logisch.

Nun hoffe ich das ich an Hand Deines Beispiels das auch noch verstehe um es dann auf andere Apps zu projezieren.

Vielleicht spart es Dir Tipparbeit wenn Du es an Hand meiner Mini-App versuchst zu erklären/darzustellen??
Ich habe das nun so ergänzt das ich auch noch eine Model Klasse habe, damit ich auch verstehe wie das Model sauber eingebunden wird.
Hier nun mal als Referenz meine Klassen und FXML Dateien - hoffe es hilft Dir vielleicht.
1. Main.java
Java:
package application;

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        try {
            // FXMLLoader loader = new FXMLLoader(getClass().getResource("Box1.fxml"));
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(this::controllerForClass);
            loader.setLocation(getClass().getResource("Box1.fxml"));
            Parent root = loader.load();
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Controller1");
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
2. Controller1.java
Java:
package application;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class Controller1 {

    Model model = new Model();

    @FXML
    private Controller2 controller2;

    @FXML
    VBox box1;

    @FXML
    private TextField textField1;

    @FXML
    Button startCtrl2;

    @FXML
    Button setFromModel;

    public void initialize() {
        textField1.setText("The quick brown fox jumps over the lazy dog");
    }

    public void startCtrl2() throws IOException {
        // FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
        FXMLLoader loaderBox2 = new FXMLLoader();
        loaderBox2.setControllerFactory(this::controllerForClass);
        loaderBox2.setLocation(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        this.controller2 = loaderBox2.getController(); // controller aus dem Loader bekommen
        this.controller2.setController1(this); // Setter auf dem Controller (controller2) aufrufen, um controller1 dort
                                                // zu setzen
        Stage stageBox2 = new Stage();
        stageBox2.initModality(Modality.APPLICATION_MODAL);
        stageBox2.setOpacity(1);
        stageBox2.setTitle("Controller2");
        Scene sceneBox2 = new Scene(rootBox2);
        sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stageBox2.setScene(sceneBox2);
        stageBox2.show();
    }

    public String getTextField1Data() {
        return textField1.getText();
    }

    private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void setFromModelTapped() {
        textField1.setText(model.returnString());
    }

}
3. Controller2.java
Java:
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class Controller2 {

    public Controller2() {
        super();
    }

    Model model = new Model();

    @FXML
    private Controller1 controller1;

    public void setController1(Controller1 controller1) {
        this.controller1 = controller1;
    }

    @FXML
    VBox box2;

    @FXML
    TextField textField2;

    @FXML
    Button setLabel;

    @FXML
    Button setFromModel;

    public void initialize() {
    }

    @FXML
    public void setInfoTextField2() {
        textField2.setText(controller1.getTextField1Data());
    }

    @FXML
    public void setFromModelTapped() {
        textField2.setText(model.returnString());
    }

}
4. Model.java
Java:
package application;

public class Model {

    public String returnString() {
        String returnString = "String from Model. Number is: " + randomNumberRange(1, 100);
        return returnString;
    }

    public static int randomNumberRange(int min, int max) {
        int n = (max + 1 - min) + min;
        return (int) (Math.random() * n);
    }

}
5. Box1.fxml
Java:
<?xml version="1.0" encoding="UTF-8"?>

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

<VBox fx:id="box1" prefHeight="194.0" prefWidth="429.0"
    xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="application.Controller1">
    <children>
        <HBox alignment="CENTER" prefHeight="40.0" prefWidth="337.0">
            <children>
                <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Controller1">
                    <font>
                        <Font name="System Bold" size="18.0" />
                    </font>
                </Text>
            </children>
            <VBox.margin>
                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
            </VBox.margin>
        </HBox>
        <HBox alignment="CENTER_LEFT" prefHeight="72.0" prefWidth="337.0">
            <children>
                <TextField fx:id="textField1" editable="false"
                    prefHeight="30.0" prefWidth="410.0" />
            </children>
            <VBox.margin>
                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
            </VBox.margin>
        </HBox>
        <HBox alignment="CENTER" prefHeight="53.0" prefWidth="429.0">
            <VBox.margin>
                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
            </VBox.margin>
            <children>
                <Button fx:id="setFromModel" mnemonicParsing="false"
                    onAction="#setFromModelTapped" text="Set from Model" />
                <Button fx:id="startCtrl2" mnemonicParsing="false"
                    onAction="#startCtrl2" text="Start Controller2">
                    <HBox.margin>
                        <Insets bottom="10.0" left="30.0" top="10.0" />
                    </HBox.margin>
                </Button>
            </children>
        </HBox>
    </children>
</VBox>
6. Box2.fxml
Java:
<?xml version="1.0" encoding="UTF-8"?>

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

<VBox fx:id="box2" prefHeight="208.0" prefWidth="320.0"
    xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="application.Controller2">
    <children>
        <HBox alignment="CENTER" prefHeight="55.0" prefWidth="320.0">
            <children>
                <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Controller2">
                    <font>
                        <Font size="18.0" />
                    </font>
                </Text>
            </children>
            <VBox.margin>
                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
            </VBox.margin>
        </HBox>
        <HBox alignment="CENTER_LEFT" prefHeight="62.0" prefWidth="320.0">
            <children>
                <TextField fx:id="textField2" editable="false"
                    prefHeight="25.0" prefWidth="301.0" />
            </children>
            <VBox.margin>
                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
            </VBox.margin>
        </HBox>
        <HBox alignment="CENTER" prefHeight="47.0" prefWidth="320.0">
            <children>
                <Button fx:id="setFromModel" mnemonicParsing="false"
                    onAction="#setFromModelTapped" text="Set from Model" />
                <Button fx:id="setLabel" mnemonicParsing="false"
                    onAction="#setInfoTextField2" text="Set from Ctrl1">
                    <HBox.margin>
                        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                    </HBox.margin>
                </Button>
            </children>
        </HBox>
    </children>
</VBox>

Gruß
Ralf
 

mihe7

Top Contributor
Allerdings kommt die NPE auch hier wenn ich in der initialize() Methode versuche den Text zu setzen ...
Du musst über die Factory injecten, dann wird der Setter noch vor initialize aufgerufen. D. h. in startCtrl2() die Zeilen
Java:
        this.controller2.setController1(this); // Setter auf dem Controller (controller2) aufrufen, um controller1 dort
                                                // zu setzen
entfernen und dafür in die Methode controllerForClass "verschieben":
Java:
    private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            if (clazz.equals(Controller2.class)) {
                Controller2 ctrl2 = (Controller2) controllerInstance;
                ctrl2.setController1(this);
            }
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
Nicht getestet, sollte aber funktionieren, d. h. Du solltest dann in Controller2#initialize das machen können, was Du ursprünglich tun wolltest:
Java:
    public void initialize() {
        setInfoTextField2();
    }
 

ralfb1105

Bekanntes Mitglied
Hallo mihe7,

:) Danke - damit funktioniert es in meinem kleinen Beispiel wie von dzim beschrieben.
Genau genommen bin ich wirklich ein Depp, denn der Kommentar von dzim
// controllerInstance#someArbitraryMethodCall if necessary
// will be triggered *before* the initialize method!
sagt es ja eigentlich schon, nur das ich es nicht im Detail verstanden habe was genau die Factory nun macht/bewirkt und somit auch nicht kapiert habe das genau dier die Übergabe stattfinden muss o_O

Mal shen was dzim nun zu meiner Implementierung sagt und vielleicht macht er ja noch ein Beispiel, z.B. mit dem erwähnten Interface das es ggf. noch deutlicher macht.

Dann ist noch die Frage zu folgender Aussage
Zum Model selbst: Wenn du es immer brav überall rein gibst, ist der beste Weg, dein Model-Objekt im Main anzulegen (start-Methode der Applikation-Klasse) und dann zu übergeben...
Da bin ich mir auch nicht ganz sicher ... ich habe das Model in Controller1 und Controller2 erstellt:
Java:
Model model = new Model();
aber so wie ich dzim verstehe sollte man das in der Main start() Methode machen und dann übergeben .. ?? ist das dann auch wieder mit einem Setter in den Controllern zu machen?

Danke nochmal für Eure Geduld und die professionelle Unterstützung!!!
Gruß
Ralf
 

Robat

Top Contributor

ralfb1105

Bekanntes Mitglied
Hallo Robat,

Deswegen das Model zentral in der Main erstellen und dann in die jeweiligen Controller geben.

Das mit dem Model erstellen in der Main würde ich dann so machen
Java:
private Model Model = new Model();

Dann aber die Frage wie ich in der start() Methode von Main die Controller am Besten bekomme. Würde ich das dann auch für die beiden Controller aus einen FXMLLoader machen, wie z.B. in Controller1 wo ich Controller2 hole:
Java:
this.controller2 = loaderBox2.getController();
.

Gruß
Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo,
ich habe es mal über den FXMLLoader folgendermassen probiert.
Java:
FXMLLoader loaderBox1 = new FXMLLoader();
            loaderBox1.setControllerFactory(this::controllerForClass);
            loaderBox1.setLocation(getClass().getResource("Box1.fxml"));
            this.controller1 = loaderBox1.getController(); // controller aus dem Loader bekommen
            Parent rootBox1 = loaderBox1.load();
Danach in der Factory, folgendes gemacht:
Java:
if (clazz.equals(Controller1.class)) {
                System.out.println("Clazz: " + clazz);
                Controller1 ctrl1 = (Controller1) controllerInstance;
                ctrl1.setModel(model);
            }
Das funktioniert ohne NPE oder anderer Excpetion für den Controller1, sprich ich kann über den Button auf das Model zugreifen und der erzeugte String wird im TextField gesetzt.

Wenn ich das allerding für Controller2 so versuche gibt es eine NPE im Controller2. Ich habe es genauso wie bei Controller1 probiert - in der Main:
Java:
FXMLLoader loaderBox2 = new FXMLLoader();
            loaderBox2.setControllerFactory(this::controllerForClass);
            loaderBox2.setLocation(getClass().getResource("Box2.fxml"));
            this.controller2 = loaderBox2.getController();
            Parent rootBox2 = loaderBox2.load();
und dann in der Factory ergänzt:
Java:
if (clazz.equals(Controller1.class)) {
                System.out.println("Clazz: " + clazz);
                Controller1 ctrl1 = (Controller1) controllerInstance;
                ctrl1.setModel(model);
            } else if (clazz.equals(Controller2.class)) {
                System.out.println("Clazz: " + clazz);
                Controller2 ctrl2 = (Controller2) controllerInstance;
                ctrl2.setModel(model);
            }
Beim starten kommt dann auch in der Console der Text aus der Factory:
Code:
Clazz: class application.Controller1
Clazz: class application.Controller2
aber gefolgt von einer Exception:
Code:
javafx.fxml.LoadException:
/C:/pdbrboer/GIT/Udeny-JavaGrundlagen/TestTwoController/bin/application/Box2.fxml

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2571)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
    at application.Main.start(Main.java:32)
    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(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.Trampoline.invoke(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2566)
    ... 12 more
Caused by: java.lang.NullPointerException
    at application.Controller2.setInfoTextField2(Controller2.java:46)
    at application.Controller2.initialize(Controller2.java:41)
    ... 22 more
Es sieht so aus als wenn das mit dem FXMLLoader nicht klappen würde ??
Code:
javafx.fxml.LoadException:
/C:/pdbrboer/GIT/Udeny-JavaGrundlagen/TestTwoController/bin/application/Box2.fxml

So einfach wie ich es mir gedacht habe ist es dann doch wieder nicht ...
Gruß
Ralf
 

Robat

Top Contributor
Hier noch mal ein kleines Beispiel. Du darfst die zweite FXML nicht schon in deiner Hauptklasse laden.
Java:
public class Test extends Application {

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

    private Model model;

    @Override
    public void start(Stage primaryStage) throws Exception {

        model = new Model();

        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(clazz -> controllerForClass(clazz, model));
        loader.setLocation(getClass().getResource("view1.fxml"));
        Parent root = loader.load();

        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static Object controllerForClass(Class<?> clazz, Model model) {
        try {
            Object controllerInstance = clazz.newInstance();

            if(ModelInjectable.class.isAssignableFrom(clazz)) {
                ModelInjectable c = (ModelInjectable) controllerInstance;
                c.setModel(model);
            }
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
}
Controller:
Java:
public class View1Controller implements ModelInjectable {
    private Model model;

    public void showView2() {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(clazz -> Test.controllerForClass(clazz, model));
            loader.setLocation(getClass().getResource("view2.fxml"));

            Stage stage = new Stage();
            stage.setScene(new Scene(loader.load()));
            stage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void setModel(Model model) {
        this.model = model;
    }
}

public class View2Controller implements ModelInjectable {

    private Model model;

    @Override
    public void setModel(Model model) {
        this.model = model;
    }
}
Sonstiges:
Java:
public class Model {
    // model class ...
}

public interface ModelInjectable {
    void setModel(Model model);
}
Edit:
controllerForClass(Class<?> clazz, Model model) ggf noch in eine eigene (Utils) Klasse auslagern
 

ralfb1105

Bekanntes Mitglied
Hallo,

ich habe es jetzt einmal folgendermassen probiert:
1. In der Main.java das Model erstellt und dort dem Controller1 übergeben:
Java:
private Model model = new Model();

public void start(Stage primaryStage) throws Exception {
        try {
            // FXMLLoader loader = new FXMLLoader(getClass().getResource("Box1.fxml"));
            FXMLLoader loaderBox1 = new FXMLLoader();
            loaderBox1.setControllerFactory(this::controllerForClass);
            loaderBox1.setLocation(getClass().getResource("Box1.fxml"));
            this.controller1 = loaderBox1.getController(); // controller aus dem Loader bekommen
            Parent rootBox1 = loaderBox1.load();   
...
...
}

private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            if (clazz.equals(Controller1.class)) {
                Controller1 ctrl1 = (Controller1) controllerInstance;
                System.out.println("Model instance Main:" + model);
                ctrl1.setModel(model);
            }
...
...
}
2. Im Controller1 das Model, sollte ja das aus Main sein, an Controller2 übergeben:
Java:
private Model model;

    public void setModel(Model model) {
        this.model = model;
        System.out.println("Model instance Controller1:" + model);
    }

public void startCtrl2() throws IOException {
        // FXMLLoader loaderBox2 = new FXMLLoader(getClass().getResource("Box2.fxml"));
        FXMLLoader loaderBox2 = new FXMLLoader();
        loaderBox2.setControllerFactory(this::controllerForClass);
        loaderBox2.setLocation(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        this.controller2 = loaderBox2.getController(); // controller aus dem Loader bekommen
...
...
}

private Object controllerForClass(Class<?> clazz) {
        try {
            Object controllerInstance = clazz.newInstance();
            if (clazz.equals(Controller2.class)) {
                Controller2 ctrl2 = (Controller2) controllerInstance;
                ctrl2.setController1(this); // Setter auf dem Controller (controller2) aufrufen, um controller1 dort zu setzen
                ctrl2.setModel(this.model);
            }
...
...
}

Damit funktioniert es erst einmal - bin mir aber nicht 100% sicher ob das so richtig ist im Sinne von JavaFX.
Die Ausgaben zeigen das ich überall die gleiche Model Instanz haben sollte:
Code:
Model instance Main:application.Model@2421c53b
Model instance Controller1:application.Model@2421c53b
Model instance Controller2:application.Model@2421c53b

Gruß

Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo Robat,

oh, da haben sich die Inputs überschnitten ;) ich schaue mir Deine Ausführungen gleich mal an .. mit dem laden der 2 FXML (Controller2) lag ich ja schon mal nicht schlecht :rolleyes:

Ich melde mich dann nochmal ...

Gruß
Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo Robat,

beim lesen habe ich eine Frage zu Deinem Beispiel.
Frage:
Müsste man das gleiche Interface, incl. der Implementierung in controllerForClass(), dann ja auch noch für ControllerInjectible erstellen um die Controller weiter zu geben? Richtig?
Java:
public interface ControllerInjectable {
    void setController(Controller ctrl);
}

Gruß
Ralf
 

Robat

Top Contributor
Genau so ist es. Du könntest also Controller1 ControllerInjectable und ModelInjectable implementieren lassen. Dann kannst du in controllerForClass() einen weiteren if-Zweig einfügen für ControllerInjectable und dann dort den Controller setzen.
 

ralfb1105

Bekanntes Mitglied
Hallo Robat,

generell sieht es schon mal nicht schlecht aus, nun überlege ich aber noch wie ich Deinen Vorschlag
controllerForClass(Class<?> clazz, Model model) ggf noch in eine eigene (Utils) Klasse auslagern

implementieren muss.
Ist das auch wieder als Injectable zu realisieren mit einem Interface IUtilsInjectable?
Wenn ich es "einfach" als neues Object in Main und Controller1 mache, gibt es beim starten von Controller2 aus Controller1 die bekannte NPE in "textField2.setText(controller1.getTextField1Data());".

Gruß
Ralf
 

Robat

Top Contributor
Der Vorschlag dient einfach nur dazu, dass du diese Methode in einer eigenen Klasse definierst. Dafür brauchst du dann kein Interface. Zum Beispiel (hab deinen Namen einfach mal übernommen)
Java:
public class InjectionUtils {
    public static Object controllerForClass(Class<?> clazz, Model model) {
        try {
            Object controllerInstance = clazz.newInstance();

            if(ModelInjectable.class.isAssignableFrom(clazz)) {
                ModelInjectable c = (ModelInjectable) controllerInstance;
                c.setModel(model);
            }
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
}
Dann benutzt die Methode jeweils wie folgt
Java:
loader.setControllerFactory(clazz -> InjectionUtils.controllerForClass(clazz, model));
 

mihe7

Top Contributor
@Robat wie immer sehr schön. Einzig andere Bezeichner würde ich wählen. Evtl. ControllerFactories für die Klasse und für die Methode newFactory.

Java:
loader.setControllerFactory(clazz -> ControllerFactories.newFactory(clazz, model));
 

ralfb1105

Bekanntes Mitglied
Hallo Robat,

verstanden - Danke!
Hier jetzt noch mals als Zusammenfassung, und wenn möglich auch noch mal als Review, meine Klassen zu dem Beispiel:
1. Main.java
Java:
package application;

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

public class Main extends Application {

    private Model model = new Model();

    @SuppressWarnings("unused")
    private Controller1 controller1;

    @Override
    public void start(Stage primaryStage) throws Exception {
        try {
            FXMLLoader loaderBox1 = new FXMLLoader();
            // Use Factory to inject Model in Controller1
            loaderBox1.setControllerFactory(clazz -> InjectionUtils.controllerForClass(clazz, model));
            loaderBox1.setLocation(getClass().getResource("Box1.fxml"));
            this.controller1 = loaderBox1.getController(); // controller aus dem Loader bekommen
            Parent rootBox1 = loaderBox1.load();
            Scene scene = new Scene(rootBox1);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Controller1");
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
2. Controller1.java
Java:
package application;

import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class Controller1 implements IModelInjectable {

    private Model model;

    @Override
    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    private Controller2 controller2;

    @FXML
    VBox box1;

    @FXML
    private TextField textField1;

    @FXML
    Button startCtrl2;

    @FXML
    Button setFromModel;

    public void initialize() {
        textField1.setText("The quick brown fox jumps over the lazy dog");
    }

    public void startCtrl2() throws IOException {
        FXMLLoader loaderBox2 = new FXMLLoader();
        // Use Factory to inject Controller1 and Model (from Main) in Controller2
        loaderBox2.setControllerFactory(clazz -> InjectionUtils.controllerForClass(clazz, model, this));
        loaderBox2.setLocation(getClass().getResource("Box2.fxml"));
        Parent rootBox2 = loaderBox2.load();
        this.controller2 = loaderBox2.getController(); // controller aus dem Loader bekommen
        Stage stageBox2 = new Stage();
        stageBox2.initModality(Modality.NONE);
        stageBox2.setOpacity(1);
        stageBox2.setTitle("Controller2");
        Scene sceneBox2 = new Scene(rootBox2);
        sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stageBox2.setScene(sceneBox2);
        stageBox2.show();
    }

    public String getTextField1Data() {
        return textField1.getText();
    }

    public void setFromModelTapped() {
        textField1.setText(model.returnString());
    }

}
3. Controller2.java
Java:
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class Controller2 implements IModelInjectable, IController1Injectable {

    public Controller2() {
        super();
    }

    private Model model;

    @Override
    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    private Controller1 controller1;

    @Override
    public void setController1(Controller1 controller1) {
        this.controller1 = controller1;
    }

    @FXML
    VBox box2;

    @FXML
    TextField textField2;

    @FXML
    Button setLabel;

    @FXML
    Button setFromModel;

    public void initialize() {
        setInfoTextField2();
    }

    @FXML
    public void setInfoTextField2() {
        textField2.setText(controller1.getTextField1Data());
    }

    @FXML
    public void setFromModelTapped() {
        textField2.setText(model.returnString());
    }

}
4. IModelInjectable.java
Java:
package application;

public interface IModelInjectable {
   
    void setModel(Model model);

}

5. IController1Injectable.java
Java:
package application;

public interface IController1Injectable {
    void setController1(Controller1 controller);

}

6. InjectionUtils.java
Java:
package application;

public class InjectionUtils {

    public static Object controllerForClass(Class<?> clazz, Model model, Controller1 controller1) {
        try {
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            Object controllerInstance = clazz.newInstance();
            // Set Model using Setter in Controller1
            if (IModelInjectable.class.isAssignableFrom(clazz)) {
                IModelInjectable c = (IModelInjectable) controllerInstance;
                c.setModel(model);
            }

            if (IController1Injectable.class.isAssignableFrom(clazz)) {
                IController1Injectable c = (IController1Injectable) controllerInstance;
                c.setController1(controller1);
            }
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Object controllerForClass(Class<?> clazz, Model model) {
        try {
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            Object controllerInstance = clazz.newInstance();
            // Set Model using Setter in Controller1
            if (IModelInjectable.class.isAssignableFrom(clazz)) {
                IModelInjectable c = (IModelInjectable) controllerInstance;
                c.setModel(model);
            }
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

}

Nicht sicher bin ich mir ob es so richtig ist die beiden controllerForClass() Methoden so zu implementieren. In der Main injecte ich ja nur das Model in Controller1, in Controller injecte ich dann Controller1 und Model in Controller2.

Wenn das aus Eurer und somit JavaFX Sicht so richtig ist würde ich das als Blueprint nehmen, da dieser Fall ja vermutlich häufiger benötigt wird wenn man MVC verwendet.

Gruß
Ralf
 

mrBrown

Super-Moderator
Mitarbeiter
@Robat wie immer sehr schön. Einzig andere Bezeichner würde ich wählen. Evtl. ControllerFactories für die Klasse und für die Methode newFactory.
Die Klasse gibt doch Controller und nicht ControllerFactories zurück?

Ich würd das noch ändern:
Java:
public static <T> T controllerForClass(Class<T> clazz, Model model) {
 

ralfb1105

Bekanntes Mitglied
OK, ich habe mal irgendwo gelesen/gehört das man Interfaces mit "I" anfangen lässt - da ich das aber auch nicht schön finde werde ich au den Rat der Spezis hören und es umbenennen :)
Gruß
Ralf
 

Robat

Top Contributor
@ralfb1105 man kann das ganze Spielchen auch noch weiter treiben und für
Java:
FXMLLoader loaderBox1 = new FXMLLoader();
// Use Factory to inject Model in Controller1
loaderBox1.setControllerFactory(clazz -> InjectionUtils.controllerForClass(clazz, model));
loaderBox1.setLocation(getClass().getResource("Box1.fxml"));
this.controller1 = loaderBox1.getController(); // controller aus dem Loader bekommen
Parent rootBox1 = loaderBox1.load();
eine eigene Klasse/Methode schreiben damit du dich nicht immer damit rumschlagen musst. Was du machst ist immer das selbe: FXML File als Ressource laden, ControllerFactory setzen und den Parent laden. Das könnte beispielhaft so aussehen:
Java:
public class ViewLoader {
    public static Parent load(String fxmlPath, (Callback<Class<?>,Object> controllerFactory) {
      Parent parent = null;   
      try {
             FXMLLoader loader = new FXMLLoader();
             loader.setControllerFactory(controllerFactory);
             loader.setLocation(ViewLoader.class.getResource(fxmlPath);
             parent = loader.load();
        } catch(...) { 
             ....
        }
        return pane;
    }
}
Verwenden kannst du das ganze dann wie fogt. Das macht den Code mEn wieder einiges leserlicher und du vermeidest doppelten Code.
Java:
Parent root = ViewLoader.load("Box1.fxml", clazz ->ControllerFactory.controllerForClass(clazz, model));
Scene scene = new Scene(root);
...
 

Robat

Top Contributor
Den doppelten Code in der Factory kann man auch vermeiden.
Java:
public class ControllerFactory {
    private static <T> T controllerForClass(Class<T> clazz, Model model, Controller1 controller1) {
        try {
            Object controllerInstance = clazz.newInstance();
            if (model != null && IModelInjectable.class.isAssignableFrom(clazz)) {
                IModelInjectable c = (IModelInjectable) controllerInstance;
                c.setModel(model);
            }

            if (controller1 != null && IController1Injectable.class.isAssignableFrom(clazz)) {
                IController1Injectable c = (IController1Injectable) controllerInstance;
                c.setController1(controller1);
            }
            return controllerInstance;
        } catch (... {
            e.printStackTrace();
            return null;
        }
    }

    public static <T> T controllerForClass(Class<T> clazz, Model model) { 
        return controllerForClass(clazz, model, null); 
    }
    public static <T> T controllerForClass(Class<T> clazz, Model model, Controller1 controller1) { 
        return controllerForClass(clazz, model, controller1); 
    }
}
 

ralfb1105

Bekanntes Mitglied
Hallo mrBrown,

dann habe ich jetzt den Ehrgeiz das wirklich schön zu machen :rolleyes:
Ich habe in der Klasse InjectionUtils den generischen Type <T> für die Methode(n) gewählt:
Java:
public static <T> T controllerForClass(Class<T> clazz, Model model, Controller1 controller1) {
        try {
...
}

Was genau meinst Du mit
Einzig andere Bezeichner würde ich wählen. Evtl. ControllerFactories für die Klasse und für die Methode newFactory.

Die Klasse ist ja jetzt "InjectionUtils" - das ist doch so OK - oder?
Soll heißen der Bezeichner für die Methode "controllerForClass" ist zu ändern?

Gruß
Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo Robat,

in Deiner beschriebenen ViewLoader Klasse gibt es einen Syntax Fehler den ich leider nicht finde da mir die Syntax mit der Lambda Expression nicht geläufig ist ... bzw. ich es nicht verstehe ...
Java:
public class ViewLoader {
    public static Parent load(String fxmlPath, (Callback<Class<?>,Object> controllerFactory) {

}

Gruß
Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo Robat, mrBrown, mihe7,

Jetzt habe ich versucht alle Optimierungs Vorschäge einzubauen, und hier nun das Ergebnis. Ich poste jetzt noch einmal alle Klassen, falls es mal jemand braucht muss er sich nicht mühsam durch die mittlerweile 5 Seiten wühlen.
1. Main.java
Java:
package application;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class Main extends Application {

    private Model model = new Model();

    @Override
    public void start(Stage primaryStage) throws Exception {
        try {
            Parent rootBox1 = ViewLoader.load("Box1.fxml", clazz -> ControllerFactory.controllerForClass(clazz, model));
            Scene scene = new Scene(rootBox1);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Controller1");
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
2. Controller1.java
Java:
package application;

import java.io.IOException;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class Controller1 implements ModelInjectable {

    private Model model;

    @Override
    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    VBox box1;

    @FXML
    private TextField textField1;

    @FXML
    Button startCtrl2;

    @FXML
    Button setFromModel;

    public void initialize() {
        textField1.setText("The quick brown fox jumps over the lazy dog");
    }

    public void startCtrl2() throws IOException {
        Parent rootBox2 = ViewLoader.load("Box2.fxml",
                clazz -> ControllerFactory.controllerForClass(clazz, model, this));
        Stage stageBox2 = new Stage();
        stageBox2.initModality(Modality.NONE);
        stageBox2.setOpacity(1);
        stageBox2.setTitle("Controller2");
        Scene sceneBox2 = new Scene(rootBox2);
        sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stageBox2.setScene(sceneBox2);
        stageBox2.show();
    }

    public String getTextField1Data() {
        return textField1.getText();
    }

    public void setFromModelTapped() {
        textField1.setText(model.returnString());
    }

}

3. Controller2.java
Java:
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class Controller2 implements ModelInjectable, Controller1Injectable {

    private Model model;

    @Override
    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    private Controller1 controller1;

    @Override
    public void setController1(Controller1 controller1) {
        this.controller1 = controller1;
    }

    @FXML
    VBox box2;

    @FXML
    TextField textField2;

    @FXML
    Button setLabel;

    @FXML
    Button setFromModel;

    public void initialize() {
        setInfoTextField2();
    }

    @FXML
    public void setInfoTextField2() {
        textField2.setText(controller1.getTextField1Data());
    }

    @FXML
    public void setFromModelTapped() {
        textField2.setText(model.returnString());
    }

}

4. ModelInjectable.java
Java:
package application;

public interface ModelInjectable {

    void setModel(Model model);

}

5. ControllerInjectable.java
Java:
package application;

public interface Controller1Injectable {
    void setController1(Controller1 controller);

}

6. ControllerFactory.java
Java:
package application;

public class ControllerFactory {

    public static <T> T controllerForClass(Class<T> clazz, Model model, Controller1 controller1) {
        try {
            // controllerInstance#someArbitraryMethodCall if necessary
            // will be triggered *before* the initialize method!
            T controllerInstance = clazz.newInstance();
            // Set Model using Setter in Controller1
            if (ModelInjectable.class.isAssignableFrom(clazz)) {
                ModelInjectable c = (ModelInjectable) controllerInstance;
                c.setModel(model);
            }

            if (Controller1Injectable.class.isAssignableFrom(clazz)) {
                Controller1Injectable c = (Controller1Injectable) controllerInstance;
                c.setController1(controller1);
            }
            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static <T> T controllerForClass(Class<T> clazz, Model model) {
        return controllerForClass(clazz, model, null);
    }

}

7. ViewLoader.java
Java:
package application;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.util.Callback;

public class ViewLoader {

    public static Parent load(String fxmlPath, Callback<Class<?>, Object> controllerFactory) {

        Parent parent = null;
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(controllerFactory);
            loader.setLocation(ViewLoader.class.getResource(fxmlPath));
            parent = loader.load();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return parent;
    }

}

Ich hoffe das ist jetzt so wie Ihr Euch das vorgestellt habt.

Stand jetzt habe ich zugegeben nicht alles verstanden, gerade das Thema mit der Lambda Expression und wie man die Controller Instanzen ermittelt/übergibt erschließt sich mir noch nicht in vollem Umfang.

Dennoch hat mich diese Session wiedet ein großes Stück nach vorn gebracht und ich bin froh das Ihr so gedulig geholfen habt und wir auch nach der 1. lauffähigen Version weiter an der Optimierung gearbeitet haben.

PS: Falls noch etwas auffällt - bin für jede Änderung bereit ;)

Gruß

Ralf
 

dzim

Top Contributor
Alter Schwede!

Da verbringt man mal nen Abend mit Game of Thrones und prügelt sich am Vormittag mit seinem Laptop rum und schon sind hier 5 (!!!) Seiten! :eek::D

Ich sehe, mein Rat ist nicht mehr nötig, aber aus Prinzip habe ich jetzt noch was zusammengeschraubt, was sich ein wenig (in Details) von eurer Lösung unterscheidet. Hier mal der Startpunkt:
https://github.com/bgmf/poc/blob/ma...dzim/tests/fx/MainWithControllerFactory2.java

Die grundsätzliche Rangehensweise bleibt gleich. Aber ich habe die Tatsache ausgenutzt, dass in FXML definierte UIs den selben FXMLLoader wie das vom Parent-UI verwenden. Also fällt die Notwendigkeit der ControllerFactory-Utils schon mal weg.
Und da ihr die reine Interface-Variante schon hattet, hab ich das jetzt mit der Pösen Reflection-Black-Magic gemacht.

FXML "FactoryInjection.fxml"
HTML:
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      fx:controller="eu.dzim.tests.fx.controller.FactoryInjectionController" spacing="5">

    <fx:define>
        <fx:include fx:id="view2" source="FactoryInjection2.fxml"/>
    </fx:define>

    <Label fx:id="counterLabel" text="Increase"/>

    <fx:reference source="view2"/>

</VBox>

FXML "FactoryInjection2.fxml"
HTML:
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      fx:controller="eu.dzim.tests.fx.controller.FactoryInjectionController2" spacing="5">

    <Button fx:id="increaseButton" text="Increase" onAction="#increase"/>

</VBox>

Entsprechend "FactoryInjectionController":
Java:
package eu.dzim.tests.fx.controller;

import eu.dzim.tests.fx.MainWithControllerFactory2;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

public class FactoryInjectionController implements MainWithControllerFactory2.ModelInjector {

    @FXML private VBox view2;
    @FXML private Object view2Controller;

    @FXML private Label counterLabel;

    private MainWithControllerFactory2.Model model = null;

    @Override
    public void setModel(MainWithControllerFactory2.Model model) {
        System.out.println(getClass().getName() + " is receiving model.");
        this.model = model;
    }

    @FXML
    private void initialize() {
        System.out.println(getClass().getName() + " is initializing.");
        System.out.println("has a sub-view: " + view2 + " -> " + view2Controller);
        counterLabel.textProperty().bind(model.counterProperty().asString());
    }

}

und "FactoryInjectionController2":
Java:
package eu.dzim.tests.fx.controller;

import eu.dzim.tests.fx.MainWithControllerFactory2;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class FactoryInjectionController2 implements MainWithControllerFactory2.ModelInjector {

    @FXML private Button increaseButton;

    private MainWithControllerFactory2.Model model = null;

    @Override
    public void setModel(MainWithControllerFactory2.Model model) {
        System.out.println(getClass().getName() + " is receiving model.");
        this.model = model;
    }

    @FXML
    private void initialize() {
        System.out.println(getClass().getName() + " is initializing.");
    }

    @FXML
    private void increase(ActionEvent actionEvent) {
        model.increaseCounter();
        actionEvent.consume();
    }
}

Und hier der Glue-Code, die Main:
Java:
package eu.dzim.tests.fx;

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MainWithControllerFactory2 extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {

        // Premise: We have a single and shared model, that will be used across the application
        // our second UI (sub-UI of the first one) will get the model and increase a counter
        // the first UI, loaded via the FXMLLoader, will display the counters value
        Model model = new Model();

        // our loader get's the shiny controller factory

        // we could use the interface mechanism, but to make it a bit different then the other solutions, we use reflection black-magic
        // although neat, note, that I don't need to generify the controller factory

        // since I will use the FXMLs sub-view mechanism (see FactoryInjection.fxml), the loader is inherited,
        // so all directly defined sub-UIs will not need to specify their own FXMLLoader with controller factory

        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(c -> controllerForClassWithName(c, "setModel", model));
        loader.setLocation(getClass().getResource("/fxml/FactoryInjection.fxml"));
        Parent root = loader.load();
        Scene scene = new Scene(root, 200, 100);

        stage.setScene(scene);
        stage.show();
    }

    // private Object controllerForClassWithInterface(Class<?> clazz, Model model) {
    //     try {
    //         Object controllerInstance = clazz.newInstance();
    //
    //         if (ModelInjector.class.isAssignableFrom(controllerInstance.getClass()))
    //             ((ModelInjector) controllerInstance).setModel(model);
    //
    //         return controllerInstance;
    //     } catch (InstantiationException | IllegalAccessException e) {
    //         e.printStackTrace();
    //         return null;
    //     }
    // }

    private Object controllerForClassWithName(Class<?> clazz, String inheritedMethodName, Object data) {
        try {
            Object controllerInstance = clazz.newInstance();

            try {
                Method method = clazz.getDeclaredMethod(inheritedMethodName, data.getClass());
                boolean accessible = method.isAccessible();
                method.setAccessible(true);
                method.invoke(controllerInstance, data);
                method.setAccessible(accessible);
            } catch (NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }

            return controllerInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public final class Model {

        private final IntegerProperty counter;

        public Model() {
            counter = new SimpleIntegerProperty(this, "counter", 0);
        }

        public int getCounter() {
            return counter.get();
        }

        public IntegerProperty counterProperty() {
            return counter;
        }

        public void setCounter(int counter) {
            this.counter.set(counter);
        }

        public void increaseCounter() {
            counter.set(counter.get() + 1);
        }
    }

    public interface ModelInjector {
        void setModel(Model model);
    }
}

Die Controller verwenden noch das Interface, ist aber eher aus Bequemlichkeit da, man könnte es - und das @Override - auch löschen und trotzdem ginge es.
 

dzim

Top Contributor
@ralfb1105 zu dem Lambda
Java:
        // am Ende:
        loader.setControllerFactory(c -> controllerForClassWithName(c, "setModel", model));
        // ist das selbe wie:
        loader.setControllerFactory(new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> param) {
                return controllerForClassWithName(param, "setModel", model);
            }
        });
        // vorausgesetzt: Das Interface, das man verwenden möchte, hat nur eine Methode (@FunctionalInterface)
        // (default-Methode zählen hier interessanterweise nicht)

Sehr vereinfacht: Ein Lambda (oder auch Methodenreferenzen) ersetzt eine Anonyme Klasse. Mehr muss man eigentlich IMHO fast nicht wissen, denn der grosse Pluspunkt ist am Ende, dass sie den Code übersichtlicher gestalten (können). Wobei man das bei grösseren Stream-Operationen jetzt gern mal in Frage stellen kann.

(Hinweis: Im Bytecode unterscheiden sich Lambda und Anonyme Klassen allerdings schon - dort wird das invokeDynamic flag, oder so, genutzt. Aber so tief steck ich nicht in allen JVM-Details drin :p)
 

ralfb1105

Bekanntes Mitglied
Hallo dzim,

erst einmal Danke für die Erklärung zu Lambda - das werde ich mir noch einmal ganz in Ruhe ansehen.
Schön das wir jetzt zwei Lösungen haben :) wobei ich nach dem heutigen Tag im Monet nicht die Muße habe Deine mal zu testen.

Ich hoffe aber Du kannst mir vielleicht bei einem weiteren problem helfen was ich mit der Lösung von Robat habe - wobei es nicht an der Lösung liegt sondern an meinem zu wenigen Know-How :(

Problem:
In meiner Applikation, in der ich den Blueprint von heute gerne implementieren möchte, habe ich jetzt die Problematik, das ich in der Main über einen Setter die primaryStage an den Controller übergebe, damit ich dort dann diese beim drücken des Exit Button schließen kann.

Jetzt habe ich das ganze FXMLLoader aber in die ViewLoader Class ausgelagert und verwende die ControllerFactory Klasse. Nun weiß ich nicht wie ich in der Main, oder wahrscheinlich sinnvoller in der ControllerFactory den Setter für die Stage aufrufen soll :(

Hier mal der Code:

Im Controller.java
Java:
public void setStage(Stage stage) {
        mainStage = stage;
        mainStage.setOnCloseRequest(event -> {
            db.removeExceptionListener();
            LOGGER.info("Close JavaFX Stage: " + stage.getTitle());
            LOGGER.info("****** Exit OraSimpleConnect FX application ******");
        });
    }

Vor dem heutigen Tag hatte ich das in der Main wie folgt aufgerufen:
Java:
public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
            Parent root = loader.load();
            Controller controller = loader.<Controller>getController();
            controller.setStage(primaryStage);
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("OraSimpleConnectFX");
            primaryStage.setResizable(false);
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Den Controller geholt und dann mit
Java:
controller.setStage(primaryStage);
den Setter aufgerufen.

Jetzt habe ich in der Main nach nur noch:
Java:
Parent root = ViewLoader.load("main.fxml", clazz -> ControllerFactory.controllerForClass(clazz, model));

Wie setze ich denn nun die Stage im Controller? Ich habe es mal mit übergeben in die ControllerFactory probiert, bin aber mit NPE gescheitert, vermute aber das dies der Weg sein müsste ...

Gruß

Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo mrBrown,

sorry, aber da musst Du für mich etwas weiter ausholen ;)
Ich habe eine Methode load() vom Type Parent und vielen Parametern, von denen ich nicht alle im Detail verstehe.
Diese Methode gibt doch ein Parent parent zurück welches ich für die scene benötige.

Wie muss ich jetzt
Du kannst load einfach ein Pair<Parent, T> zurückgeben lassen, wobei T der Controller ist

das verstehen? Eine neue Methode ? Hättest Du vielleicht einen Code Schnipsel der es mir etwas klarer macht?

Gruß
Ralf
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
ralfb1105 JavaFX Daten zwischen Controllern austauschen- neue Frage AWT, Swing, JavaFX & SWT 7
S JavaFX (Best Practise) Daten zwischen Controllern austauschen AWT, Swing, JavaFX & SWT 1
B Datenübergabe zwischen Tabs und Daten speichern AWT, Swing, JavaFX & SWT 2
A Daten umherschieben zwischen zwei Klassen AWT, Swing, JavaFX & SWT 40
H JTabel - RowFilter Daten für Berechnung filtern AWT, Swing, JavaFX & SWT 6
M Daten zufällig Einlesen aus einer Datei (binäres Format) AWT, Swing, JavaFX & SWT 7
W Nullpointer Exception beim übertragen von Daten von Scene zu Scene AWT, Swing, JavaFX & SWT 6
W Daten von Controller zu Controller übertragen AWT, Swing, JavaFX & SWT 7
D Columns unabhängig voneinander mit Daten füllen JavaFx AWT, Swing, JavaFX & SWT 1
H Daten aus einer XML(x83-Datei) in einem JFrame anzeigen lassen AWT, Swing, JavaFX & SWT 9
T Anbinden der Tabelle an die Daten AWT, Swing, JavaFX & SWT 5
F JavaFX Tabelle mit Daten füllen AWT, Swing, JavaFX & SWT 9
A JavaFX Daten in eine HTML-Table mit JS schreiben AWT, Swing, JavaFX & SWT 3
B AWT Bot um Daten auf Website einzugeben und die Antwort zu bekommen AWT, Swing, JavaFX & SWT 2
L Daten bearbeiten ohne GUI zu blockieren - daten haltung/zurück geben AWT, Swing, JavaFX & SWT 15
ralfb1105 Swing JComboBox update der Daten AWT, Swing, JavaFX & SWT 8
ralfb1105 Swing Dynamischer Graph zum anzeigen Perfomance Daten AWT, Swing, JavaFX & SWT 35
D MySQL Daten in JTable anzeigen AWT, Swing, JavaFX & SWT 2
K TreeTableView (cellFactory) - wie Daten in Spalten einfügen AWT, Swing, JavaFX & SWT 0
A JTable mit Daten füllen AWT, Swing, JavaFX & SWT 1
J TableView Daten werden nicht ausgegeben AWT, Swing, JavaFX & SWT 9
A Swing Wie Daten in der Form speichern? Array oder ArrayList AWT, Swing, JavaFX & SWT 2
C Swing Daten in JTable wiedergeben per TableModel und MVC Pattern AWT, Swing, JavaFX & SWT 16
T JavaFX Model Daten übergeben AWT, Swing, JavaFX & SWT 4
D Swing Größe einer JComboBox im GridBagLayout aufgrund der maximalen Länge der enthaltenen Daten AWT, Swing, JavaFX & SWT 7
J JavaFX Tableview Daten hinzufügen aus anderer Klasse AWT, Swing, JavaFX & SWT 7
J Tableview Daten hinzufügen und aktualisieren AWT, Swing, JavaFX & SWT 5
S AWT Daten über TextField und Button in array speichern AWT, Swing, JavaFX & SWT 5
G Event Handling TableView daten in ein neues Fenster herauslesen? AWT, Swing, JavaFX & SWT 3
thet1983 JavaFX TableView Objekt Daten anzeige AWT, Swing, JavaFX & SWT 2
L JavaFX Verzögerung beim Laden von Daten AWT, Swing, JavaFX & SWT 6
L Daten in neuem Fenster AWT, Swing, JavaFX & SWT 2
Tort-E JavaFX Daten an WebView Komponente AWT, Swing, JavaFX & SWT 1
I JTable: Doppelklick auf Table soll neues Fenster öffnen und Daten aus JTable anzeigen AWT, Swing, JavaFX & SWT 4
S JTable Daten aus Array AWT, Swing, JavaFX & SWT 9
M Java FX Daten an Controller übergeben AWT, Swing, JavaFX & SWT 3
S JList ist leer, aber DefaultListModel hat die Daten? AWT, Swing, JavaFX & SWT 9
M JavaFX Von FXML-Controllerdatei Daten zurückgeben AWT, Swing, JavaFX & SWT 6
F JavaFX Daten aus Tabelle in ComboBox AWT, Swing, JavaFX & SWT 9
B Swing JTable mit Daten - Reihen-Größe anpassen AWT, Swing, JavaFX & SWT 0
D Applet GWT speichert Daten nicht in Datenbank AWT, Swing, JavaFX & SWT 2
S SWT In Listen den Einträgen Daten zuordnen AWT, Swing, JavaFX & SWT 2
T SWT Table (mit Spinner Spalte) Daten auslesen AWT, Swing, JavaFX & SWT 4
F Swing JTable: Daten voreinstellen AWT, Swing, JavaFX & SWT 4
B Daten in eine JTable schreiben AWT, Swing, JavaFX & SWT 3
H Swing JfreeChart aktualisieren - mit daten aus thread AWT, Swing, JavaFX & SWT 3
J valueChanged()-Methode liefert unbrauchbare Daten AWT, Swing, JavaFX & SWT 4
D SWT TreeViewer: Daten aus Model gelöscht... trotzdem noch im Baum AWT, Swing, JavaFX & SWT 4
D Daten von JDialog an JTable in JFrame übergeben AWT, Swing, JavaFX & SWT 7
O JTable zeigt die alte Daten wieder AWT, Swing, JavaFX & SWT 23
H JTable zeigt keine Daten an AWT, Swing, JavaFX & SWT 5
M Swing Daten von JPanel zu JFrame senden AWT, Swing, JavaFX & SWT 13
C Swing JTable Daten einfügen AWT, Swing, JavaFX & SWT 6
Ollek IndexOutOfBoundsException bei neuen Daten in JTable AWT, Swing, JavaFX & SWT 13
J Swing JTable-Daten Speichern und bei zeilen-änderung beibehalten. AWT, Swing, JavaFX & SWT 6
L [SWT] Daten und Bilder drucken AWT, Swing, JavaFX & SWT 2
M Trotz richtiger Daten, falsches Bild wird gezeichnet?! AWT, Swing, JavaFX & SWT 4
O JFrame: Daten alle paar Sekunden aktualisieren AWT, Swing, JavaFX & SWT 3
C Daten aus DB -> JTable aktualisieren klappt nicht AWT, Swing, JavaFX & SWT 16
A Swing(Daten zugreifen) AWT, Swing, JavaFX & SWT 25
S JComboBox - veränderliche Daten AWT, Swing, JavaFX & SWT 6
T Daten mittels ComboBox in ein JTable adden AWT, Swing, JavaFX & SWT 7
Ollek Realisierung: JTable zur Laufzeit mit neuen Daten füllen AWT, Swing, JavaFX & SWT 3
GianaSisters Swing jTable Daten unsichtbar AWT, Swing, JavaFX & SWT 12
T JTree Daten in DB schreiben am besten SQL AWT, Swing, JavaFX & SWT 21
W Daten auf Textfeld in anderer Klasse verwenden AWT, Swing, JavaFX & SWT 4
T Daten der gesamten Woche anzeigen AWT, Swing, JavaFX & SWT 2
D Problem mit JFileChooser -> Daten werden mit anführungsstriche in JTable geschrieben AWT, Swing, JavaFX & SWT 8
H JList zeigt die Daten aus dem Array nicht an! AWT, Swing, JavaFX & SWT 13
E Daten von JDialog nach JFrame übertragen AWT, Swing, JavaFX & SWT 4
K TableModel mit Daten aus Datenbank füllen AWT, Swing, JavaFX & SWT 7
M JTree mit eigenen Knoten - Zugriff auf Daten AWT, Swing, JavaFX & SWT 6
L Swing Daten in JTable aktualisieren AWT, Swing, JavaFX & SWT 5
H Swing Tabelle nach Klick auf Button mit Daten füllen? AWT, Swing, JavaFX & SWT 2
B Swing JList Daten löschen AWT, Swing, JavaFX & SWT 13
R Swing XML Datei in JTree, Problem mit Daten einlesen AWT, Swing, JavaFX & SWT 34
A SWT: Daten in Raster mit Gitternetzlinien darstellen AWT, Swing, JavaFX & SWT 7
C Swing JTextField-Daten über mehrere Dialoge AWT, Swing, JavaFX & SWT 18
K TableModel Daten übergeben AWT, Swing, JavaFX & SWT 9
Milady Swing Änderung von Daten mit sql AWT, Swing, JavaFX & SWT 2
J Daten aus einer Textdatei in ArrayList holen AWT, Swing, JavaFX & SWT 9
X Swing Daten aus Vector ins TableModel ?? AWT, Swing, JavaFX & SWT 12
T Swing Liste von Daten anzeigen AWT, Swing, JavaFX & SWT 4
S Swing Daten aus Datenbank in JTable AWT, Swing, JavaFX & SWT 2
E JCombobox mit Daten aus DB füllen AWT, Swing, JavaFX & SWT 9
L Daten werden in JTable nicht angezeigt:( AWT, Swing, JavaFX & SWT 6
Milady Annzeigen alle Daten aus der Tabelle AWT, Swing, JavaFX & SWT 6
P Swing Liste von Daten auf JFrame AWT, Swing, JavaFX & SWT 6
E Daten aus sortierter Tabelle lesen AWT, Swing, JavaFX & SWT 2
X Swing JList -> Daten anders darstellen als im Model hinterlegt -> möglich ? AWT, Swing, JavaFX & SWT 9
X Doppelte Daten in DefaultListModel unterbinden AWT, Swing, JavaFX & SWT 3
ModellbahnerTT SWT Daten zur nächsten WizardPage senden AWT, Swing, JavaFX & SWT 4
J Daten in JTable nach Sortierung auslesen AWT, Swing, JavaFX & SWT 2
hdi Swing JTable & Filtern: Daten nicht in der View AWT, Swing, JavaFX & SWT 2
S JTable Bug - eingegebene Daten werden falsch geparst :( AWT, Swing, JavaFX & SWT 4
L AWT Daten vom Dialog an Frame senden AWT, Swing, JavaFX & SWT 3
hdi Swing JTable: Löschen vom Daten im Model AWT, Swing, JavaFX & SWT 7
U JTable mit Daten aus einer HashMap füllen AWT, Swing, JavaFX & SWT 8
S Swing Bei Klick auf OK Daten übergeben AWT, Swing, JavaFX & SWT 6
A Daten aus einem Frame werden im nächsten nicht gesehen AWT, Swing, JavaFX & SWT 17

Ähnliche Java Themen

Neue Themen


Oben