JavaFX: Wie wird die Bühne während der Initialisierung vom Controller abgerufen?

89

Ich möchte Bühnenereignisse (dh Verstecken) aus meiner Controller-Klasse behandeln. Alles was ich tun muss, ist einen Listener über hinzuzufügen

((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);

Das Problem ist jedoch, dass die Initialisierung direkt danach beginnt

Parent root = FXMLLoader.load(getClass().getResource("MyGui.fxml"));

und davor

Scene scene = new Scene(root);
stage.setScene(scene);

Daher gibt .getScene () null zurück.

Die einzige Problemumgehung, die ich selbst gefunden habe, ist das Hinzufügen eines Listeners zu myPane.sceneProperty (). Wenn es nicht null wird, erhalte ich eine Szene, und füge es zu .windowProperty () hinzu. My! Goddamn! Hörer-Handling, das ich endlich abrufe. Und alles endet damit, dass die gewünschten Zuhörer auf Bühnenereignisse eingestellt werden. Ich denke, es gibt zu viele Zuhörer. Ist es der einzige Weg, mein Problem zu lösen?

Chechulin
quelle

Antworten:

116

Sie können die Instanz des Controllers von der FXMLLoaderAfter-Initialisierung über abrufen getController(), müssen dann jedoch eine instanziieren, FXMLLoaderanstatt die statischen Methoden zu verwenden.

Ich würde die Bühne passieren, nachdem ich danach load()direkt beim Controller angerufen habe :

FXMLLoader loader = new FXMLLoader(getClass().getResource("MyGui.fxml"));
Parent root = (Parent)loader.load();
MyController controller = (MyController)loader.getController();
controller.setStageAndSetupListeners(stage); // or what you want to do
zhujik
quelle
1
Denken Sie, Sie vermissen eine Besetzung? Parent root = (Parent) loader.load ();
Paul Eden
12
Ist das wirklich der beste Weg, dies zu tun? Es gibt nichts vom JavaFX-Framework bereitgestellt, um dies zu archivieren?
Hannes
1
Aus irgendeinem Grund funktioniert dies nicht, wenn Sie den Konstruktor ohne Parameter verwenden und die URL an die load()Methode übergeben. (Auch der Javadoc on getControllerklingt so, als ob er eingeschaltet sein sollte setController.)
Bombe
4
@Bombe Dies liegt daran, dass die load () -Methode mit dem URL-Parameter eine statische Methode ist, die für die Instanz, auf der Sie sie aufrufen, nichts festlegt.
Zhujik
@ Sebastian, autsch, in der Tat. Mein Fehler.
Bombe
109

Alles, was Sie brauchen, ist, AnchorPaneeinen Ausweis zu geben , und dann können Sie den Stagedaraus erhalten.

@FXML private AnchorPane ap;
Stage stage = (Stage) ap.getScene().getWindow();

Von hier aus können Sie das hinzufügen Listener, was Sie benötigen.

Bearbeiten: Wie von EarthMind unten angegeben, muss es nicht das AnchorPaneElement sein; Es kann jedes Element sein, das Sie definiert haben.

Robert Martin
quelle
2
Kurz und bündig. Vielen Dank
Sedrick
7
Beachten Sie, dass elementdies ein beliebiges Element mit einem fx:idin diesem Fenster sein kann : Stage stage = (Stage) element.getScene().getWindow();. Wenn Sie beispielsweise nur ein Buttonmit einem fx:idin Ihren Fenstern haben, verwenden Sie dieses, um die Bühne zu erhalten.
EarthMind
26
getScene()kehrt immer noch zurück null.
m0skit0
3
Das ist die Antwort, ordentlich!
Destan
12
Bevor die Initialisierung abgeschlossen ist (z. B. in der Controller-Initialisierungsmethode), funktioniert dies nicht, da getScene () null zurückgibt. Das Originalplakat impliziert, dass dies seine Situation ist. In diesem Fall ist die von Utku Özdemir unten angegebene Alternative 1 besser. Wenn Sie die Bühne nicht benötigen, reicht ein Listener aus. Ich verwende dies in initialize (), um eine ImageView-Dehnung zu erstellen:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie
31

Ich weiß, es ist nicht die Antwort, die Sie wollen, aber IMO sind die vorgeschlagenen Lösungen nicht gut (und Ihr eigener Weg ist). Warum? Weil sie vom Anwendungsstatus abhängen. In JavaFX hängen ein Steuerelement, eine Szene und eine Bühne nicht voneinander ab. Dies bedeutet, dass ein Steuerelement ohne Hinzufügen zu einer Szene leben kann und eine Szene existieren kann, ohne an eine Bühne gebunden zu sein. Und dann kann zu einem Zeitpunkt t1 die Steuerung an eine Szene angehängt werden, und zum Zeitpunkt t2 kann diese Szene zu einer Bühne hinzugefügt werden (und das erklärt, warum sie beobachtbare Eigenschaften voneinander sind).

Der Ansatz, der vorschlägt, die Controller-Referenz abzurufen und eine Methode aufzurufen und die Stufe an diese zu übergeben, fügt Ihrer Anwendung einen Status hinzu. Dies bedeutet, dass Sie diese Methode im richtigen Moment direkt nach dem Erstellen der Bühne aufrufen müssen. Mit anderen Worten, Sie müssen jetzt einer Reihenfolge folgen: 1- Erstellen der Stufe 2- Übergeben Sie diese erstellte Stufe über eine Methode an die Steuerung.

Sie können (oder sollten) diese Reihenfolge bei diesem Ansatz nicht ändern. Sie haben also die Staatenlosigkeit verloren. Und in der Software ist der Zustand im Allgemeinen böse. Im Idealfall sollten Methoden keine Aufrufreihenfolge erfordern.

Was ist die richtige Lösung? Es gibt zwei Alternativen:

1- Ihr Ansatz, in den Controller Listening-Eigenschaften, um die Bühne zu bekommen. Ich denke, das ist der richtige Ansatz. So was:

pane.sceneProperty().addListener((observableScene, oldScene, newScene) -> {
    if (oldScene == null && newScene != null) {
        // scene is set for the first time. Now its the time to listen stage changes.
        newScene.windowProperty().addListener((observableWindow, oldWindow, newWindow) -> {
            if (oldWindow == null && newWindow != null) {
                // stage is set. now is the right time to do whatever we need to the stage in the controller.
                ((Stage) newWindow).maximizedProperty().addListener((a, b, c) -> {
                    if (c) {
                        System.out.println("I am maximized!");
                    }
                });
            }
        });
    }
});

2- Sie tun, was Sie tun müssen, wo Sie das erstellen Stage(und das ist nicht das, was Sie wollen):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
            if (c) {
                System.out.println("I am maximized!");
            }
        });
stage.setScene(someScene);
...
Utku Özdemir
quelle
1
Dies ist die kürzeste JavaFX-Referenz auf dem Planeten !! Danke, Utku!
Aram Paronikyan
Interessante Sichtweise. Danke :)
Utku Özdemir
Ich versuche, diesen Ansatz zu verwenden, um eine TableView zu füllen und einen ProgressIndicator-zentrierten Respekt für die TableView anzuzeigen, aber es stellt sich heraus, dass die Werte für getX () und getY () innerhalb der Ereignisse nicht dieselben sind, als ob Sie sie nach der gesamten Initialisierung verwenden würden Prozessende (innerhalb eines Klickereignisses in einer Schaltfläche) entweder für Szene und Bühne, sogar Bühne getX () und getY () geben NaN zurück, irgendeine Idee?
Leobelizquierdo
@leobelizquierdo, habe dieses getY () Problem in diesem Moment, hast du eine Lösung gefunden?
JonasAnon
13

Der einfachste Weg, um ein Bühnenobjekt in der Steuerung abzurufen, ist:

  1. Fügen Sie eine zusätzliche Methode in der selbst erstellten Controller-Klasse hinzu, z. B. (dies ist eine Setter-Methode zum Festlegen der Stufe in der Controller-Klasse).

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
  2. Holen Sie sich den Controller in die Startmethode und stellen Sie die Bühne ein

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
  3. Jetzt können Sie im Controller auf die Bühne zugreifen

Sandeep Kumar
quelle
Dies war der Gewinner für mich. Robert Martins Antwort funktionierte zuerst, warf dann aber einen Fehler aus, den ich dadurch behoben stagehabe.
Dammeul
1

Weisen Sie fx: id zu oder deklarieren Sie eine Variable für einen beliebigen Knoten: Anchorpane, Button usw. Fügen Sie dann einen Ereignishandler hinzu und fügen Sie in diesen Ereignishandler den folgenden Code ein:

Stage stage = (Stage)((Node)((EventObject) eventVariable).getSource()).getScene().getWindow();

Hoffe, das funktioniert bei dir !!

Ankit RajDeo
quelle
1

Platform.runLater verhindert die Ausführung, bis die Initialisierung abgeschlossen ist. In diesem Fall möchte ich jedes Mal eine Listenansicht aktualisieren, wenn ich die Fensterbreite verändere.

Platform.runLater(() -> {
    ((Stage) listView.getScene().getWindow()).widthProperty().addListener((obs, oldVal, newVal) -> {
        listView.refresh();
    });
});

in deinem Fall

Platform.runLater(()->{
    ((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
});
AdamOutler
quelle
-3

Sie können mit erhalten node.getScene, wenn Sie nicht von anrufen Platform.runLater, ist das Ergebnis ein Nullwert.

Beispiel Nullwert:

node.getScene();

Beispiel kein Nullwert:

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
fjqa86
quelle