JavaFX Daten zwischen Controller "austauschen"

Keine Garantie, dass es syntaktisch passt, aber etwa so:

Java:
public static <T> Pair<Parent,T> load(String fxmlPath, Callback<Class<T>, Object> controllerFactory) {

        Parent parent = null;
        T controller = null;
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(controllerFactory);
            loader.setLocation(ViewLoader.class.getResource(fxmlPath));
            parent = loader.load();
            controller = loader.getController();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new Pair(parent, controller);
    }
 
Hm, oh man ... da bin ich echt überfordert ... versucche es aber trotzdem ...

Ich habe das jetzt so verstanden das ich diese neue Methode vom Type /Klasse Pair in z.B. ViewLoader implementiere.
Das habe ich wie folgt gemacht:
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 (Exception e) {
            e.printStackTrace();
        }
        return parent;
    }
  
    public static <T> Pair<Parent,T> load1(String fxmlPath, Callback<Class<T>, Object> controllerFactory) {

        Parent parent = null;
        T controller = null;
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(controllerFactory);
            loader.setLocation(ViewLoader.class.getResource(fxmlPath));
            parent = loader.load();
            controller = loader.getController();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new Pair<Parent, T>(parent, controller);
    }

}

Jetzt meckert Eclipse bei der Zeile
Java:
loader.setControllerFactory(controllerFactory);
mit folgendem Fehler:
Code:
The method setControllerFactory(Callback<Class<?>,Object>) in the type FXMLLoader is not applicable for the arguments (Callback<Class<T>,Object>)

Die Definition ist ja:

Code:
setControllerFactory
public void setControllerFactory(Callback<Class<?>,Object> controllerFactory)
Sets the controller factory used by this serializer.
Parameters:
controllerFactory -
 
Hallo,
ich habe es jetzt mal so geändert, und dann kommt in Eclipse zumindest keine Meldung Fehlermeldung mehr:
Java:
public static <T> Pair<Parent,T> load1(String fxmlPath, Callback<Class<?>, Object> controllerFactory) {

        Parent parent = null;
        T controller = null;
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(controllerFactory);
            loader.setLocation(ViewLoader.class.getResource(fxmlPath));
            parent = loader.load();
            controller = loader.getController();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new Pair<Parent, T>(parent, controller);
    }

Dann habe ich das mal testweise in der Main so versucht:
Java:
Pair pair = ViewLoader.load1("Box1.fxml", clazz -> ControllerFactory.controllerForClass(clazz, model));

Jetzt weiß ich aber nicht wie ich die Werte aus dem pair bekomme ??
Ein sysout von pair zeigt mir folgendes:
Java:
Pair: VBox[id=box1]=application.Controller1@30ea955c

Wenn Pair <key,value> ist, dann habe ich doch jetzt mein Parent "VBox" und das Value "application.Controller1@30ea955c".

Ich habe es dann mal mit folgender Zuweisung versucht:
Java:
Controller1 controller1 = (Controller1) pair.getKey();
das geht aber schief da der Cast nicht geht:
Code:
java.lang.ClassCastException: javafx.scene.layout.VBox cannot be cast to application.Controller1

Wie komme ich denn jetzt von dem Pair zu meinem Controller Objekt??

Gruß
Ralf
 
Oh man, ich mache Feierabend ... jetzt habe ich mal selber ein bisschen hinbekommen und dann so ein Tippfehler ... ich meinet getValue ... könnte mich echt ärgern 😳
Damit habe ich den Controller 🙂
Jetzt mache ich erst einmal eine Pause .. der Hund hat keine Pampers um 😀 .. und dann melde ich mich nachher noch einmal ob es mit dem setStage funktioniert hat.

Danke schon mal.

Gruß
Ralf
 
Hallo zusammen,

was soll ich sagen ... ich bekomme es nicht hin 😡
Ich habe es so versucht wie in meiner App wo es mit der übergabe der Stage in den Controller mit Hilfe eines Setter funktioniert, in meiner Test App geht es so nicht und ich finde den Fehler nicht.
Hier mal mein Versuch:
1. In der Main App den Controller holen und mit Hilfe der Setter Methode die Stage primaryStage setzen.
Java:
Pair<Parent, Controller1> pair = ViewLoader.load1("Box1.fxml", clazz -> ControllerFactory.controllerForClass(clazz, model));
            Controller1 controller1 = (Controller1) pair.getValue();
            System.out.println("Pair: " + pair);
            System.out.println("Controller: " + controller1);
            System.out.println("Stage: " + primaryStage);
            controller1.setStage(primaryStage);
2. Im Controller die Setter Methode.
Java:
private Stage mainStage;

public void setStage(Stage stage) {
        this.mainStage = stage;
        System.out.println("Stage: " + mainStage + " Title: " + mainStage.getTitle());
        this.mainStage.setOnCloseRequest(event -> {
            System.out.println("Close JavaFX Stage: " + mainStage + " Title: " + mainStage.getTitle());
        });
    }

Wenn ich das so starte kommen folgende Debug Ausgaben in der Console:
Code:
Pair: VBox[id=box1]=application.Controller1@12a7d0f6
Controller: application.Controller1@12a7d0f6
Stage: javafx.stage.Stage@6ce01a3a
Stage: javafx.stage.Stage@6ce01a3a Title: null

So wie ich das verstehen würde ist das erst einmal OK - die Stage ist im Controller "angekommen".

Jetzt das Problem. Ich habe im Controller1 einen Exit Button definiert und im SceneBuilder zugewiesen.
Java:
public void exitButtonTapped(ActionEvent event) {
        System.out.println("Stage: " + mainStage + " Title: " + mainStage.getTitle());
        mainStage.fireEvent(new WindowEvent(mainStage, WindowEvent.WINDOW_CLOSE_REQUEST));
    }

Wenn ich die Applikation starte und den Exit Button drücke kommt eine NPE für folgende Zeile:
Java:
System.out.println("Stage: " + mainStage + " Title: " + mainStage.getTitle());
sprich er kennt mainStage nicht !!????? .. meine Vermutung.

Schließe ich das Fenster über den Fenster "x" Button kommt folgende Meldung in der Konsole:
Code:
Close JavaFX Stage: javafx.stage.Stage@62181891 Title: Controller1
was für mich bedeutet der setStage mit dem setOnCloseRequest event funktioniert.

Ich verstehe nicht was ich jetzt schon wieder übersehen habe ...
Kann mir jemand auf die Sprünge helfen?

Gruß

Ralf
 
Hallo,
hier noch ein paar weitere Diagnose Steps ...
Wenn ich in der Main.java den FXMLLoader auf die "alte" Weise durchführe, sprich ohne den ViewLoader, und auch den Controller ohne das Pair hole, dann funktioniert es !???
Hier mal die Main.java mit der es funktioniert:
Java:
FXMLLoader loader = new FXMLLoader(getClass().getResource("Box1.fxml"));
            Parent rootBox1 = loader.load();
            Controller1 controller1 = loader.<Controller1>getController();
            //Parent rootBox1 = ViewLoader.load("Box1.fxml", clazz -> ControllerFactory.controllerForClass(clazz, model));
            //Pair<Parent, Controller1> pair = ViewLoader.load1("Box1.fxml", clazz -> ControllerFactory.controllerForClass(clazz, model));
            //Controller1 controller1 = (Controller1) pair.getValue();
            //System.out.println("Pair: " + pair);
            System.out.println("Controller: " + controller1);
            System.out.println("Stage: " + primaryStage);
            controller1.setStage(primaryStage);
Die Ausgaben in der Console wenn ich dann im Controller1 den Exit Button drücke:
Code:
Controller: application.Controller1@67c880b2
Stage: javafx.stage.Stage@1c30e6bc
Stage: javafx.stage.Stage@1c30e6bc Title: null
Stage: javafx.stage.Stage@1c30e6bc Title: Controller1
Close JavaFX Stage: javafx.stage.Stage@1c30e6bc Title: Controller1

Der Unterschied, bzw. mein Problem/Fehler, muss also irgendwo in der ControllerFactory oder ViewLoader liegen, obwohl das Controller1 Objekt was ich mir hole erst einmal OK ist ...

Dieses Zeug raubt mir noch den Verstand ...

Gruß
Ralf
 
Also ich müsste jetzt mutmaßen... Und ohne dein Projekt in seinem jetzigen Zustand auszuprobieren, fällt es mir schwer, da eine Aussage zu treffen.
Die ControllerFactory lädt ja nur die Klasse und pusht das Modell rein - die Stage sehe ich noch nicht.
Ich sehe aber auch den Mehrgewinn nicht, wenn man die Stage an einen Controller durchreicht, nur um dort ein #setOnCloseRequest ranzuhängen - so was mache ich eigentlich immer in der #start-Methode der Applikation.
 
Hallo dzim,

es ging in einem anderen Thread hier im Forum um das austauchen von Exceptions und da haben wir zusammen den Excpetion Listener implementiert, den ich in dieser Test Applikation hier aber nicht drin habe. Im Zuge dessen wurde mir geraten den Exception Listener wieder zu entfernen wenn das fentsre geschlossen wird.
Java:
public void setStage(Stage stage) {
        mainStage = stage;
        System.out.println("Stage: " + mainStage + " Title: " + mainStage.getTitle());
        mainStage.setOnCloseRequest(event -> {
            db.removeExceptionListener();
            LOGGER.info("Close JavaFX Stage: " + stage.getTitle());
            LOGGER.info("****** Exit OraSimpleConnect FX application ******");
        });
    }

public void removeExceptionListener() {
        exceptionListener = null;
    }

Darum habe ich das dann mit der Übergabe der Stage gemacht, was in dem Stadium ja auch funktioniert, nur halt nicht mehr mit detr Lösung mit den 2 Controllern von gestern.

Ich sehe aber auch den Mehrgewinn nicht, wenn man die Stage an einen Controller durchreicht, nur um dort ein #setOnCloseRequest ranzuhängen - so was mache ich eigentlich immer in der #start-Methode der Applikation.

Was genau meinst Du denn mit der Aussage. Den setOnCloseRequest dort schon als Event ran zu hängen?

Wäre es eine Hilfe wenn ich Dir mein Test projekt mal als ZIP zur Verfügung stelle - Dropbox ?

Gruß

Ralf
 
Hallo dzim,

ich habe das mal in der start-Methode gemacht:
Java:
primaryStage.setOnCloseRequest(event -> {
                System.out.println("Close primaryStage now ...");
            });
Das funktioniert auch wenn ich das Fenster schließe.
Nun hätte ich aber die Problematik wenn ich einen eigenen Exit Button machen möchte, oder aber z.B. das File - Close Menu.

Dann benötigt der Controller doch auch Zugriff auf die Stage um den Event abzufeuern - oder?

Gruß

Ralf
 
Du hast von *jeder* UI aus Zugriff auf die Stage. Auch ohne spezielle Injection oder so:
Java:
((Stage) myControl.getScene().getWindow()).fireEvent(new WindowEvent(((Stage) myControl.getScene().getWindow()), WindowEvent.WINDOW_CLOSE_REQUEST));
In jedem Control, also jedem Stück UI, das du auf den Scene-Graph bringst, hast du eine Referenz auf die Scene, in der das Stück UI dargestellt wird. Dieses wiederum hat Zugriff auf sein Window. Window ist die Basis-Klasse von Stage. Du kannst also im Regelfall einfach das Window nach Stage casten, et voilà.
 
😀 das ist ja mal eine Zeile ...
Damit hat es funktioniert und ich würde mir dann auch den Code für setStage und Pair zum holen des Controllers in Main sparen. Das macht es wieder etwas schlanker ... schreibe alle Klassen der hier vorgestellten Lösung 1 noch einmal um und wenn es funktioniert poste ich es - für die Nachwelt 😉

Gruß
Ralf
 
Hallo,
hier jetzt noch einmal die Klassen für das Test Projekt, welches folgende Merkmale zeigt:
  • Austausch von Daten zwischen Controller
  • Übergabe des Model an die Controller
Die Realisierung erfolgt mit Interfaces, einer eigenen Classe zum laden der Views und einer ControllerFactory Klasse.

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.setOnCloseRequest(event -> {
                System.out.println("Close Controller1 Stage  now ...");
            });
            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.event.ActionEvent;
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;
import javafx.stage.WindowEvent;

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;

   @FXML
   Button exit;

   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");
       stageBox2.setOnCloseRequest(event -> {
           System.out.println("Close Controller2 Stage  now ...");
       });
       Scene sceneBox2 = new Scene(rootBox2);
       sceneBox2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
       stageBox2.setScene(sceneBox2);
       stageBox2.show();
   }

   @FXML
   public void exitButtonTapped(ActionEvent event) {
       // Cast the Window of UI Controll Butoon exit to Stage
       ((Stage) exit.getScene().getWindow())
               .fireEvent(new WindowEvent(((Stage) exit.getScene().getWindow()), WindowEvent.WINDOW_CLOSE_REQUEST));
   }

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

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

}
3. Controller2.java
Java:
package application;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

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;

    @FXML
    Button exit;

    public void initialize() {
        setInfoTextField2();
    }

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

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

    public void exitButtonTapped(ActionEvent event) {
        ((Stage) exit.getScene().getWindow())
                .fireEvent(new WindowEvent(((Stage) exit.getScene().getWindow()), WindowEvent.WINDOW_CLOSE_REQUEST));
    }

}

4. 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;
    }

}

5. Controller1Injectable
Java:
package application;

public interface Controller1Injectable {
    void setController1(Controller1 controller);
}

6. ModelInjectable.java
Java:
package application;

public interface ModelInjectable {

    void setModel(Model model);

}

7. 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);
    }

}

8. 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);
    }

}

9. 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="200.0" prefWidth="350.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">
                    <HBox.margin>
                        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                    </HBox.margin>
                </Button>
                <Button fx:id="startCtrl2" mnemonicParsing="false"
                    onAction="#startCtrl2" text="Start Controller2">
                    <HBox.margin>
                        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                    </HBox.margin>
                </Button>
                <Button fx:id="exit" mnemonicParsing="false"
                    onAction="#exitButtonTapped" text="EXIT">
                    <HBox.margin>
                        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                    </HBox.margin>
                </Button>
            </children>
        </HBox>
    </children>
</VBox>

10. 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="200.0" prefWidth="350.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">
                    <HBox.margin>
                        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                    </HBox.margin>
                </Button>
                <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>
                <Button fx:id="exit" mnemonicParsing="false"
                    onAction="#exitButtonTapped" text="EXIT">
                    <HBox.margin>
                        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                    </HBox.margin>
                </Button>
            </children>
        </HBox>
    </children>
</VBox>

DANKE vor allem an dzim, robat, mrBrown und mihe7 für die Unterstützung!

Gruß
Ralf
 

Zurück
Oben