JavaFX: Jak pobrać stage z kontrolera podczas inicjalizacji?

91

Chcę obsługiwać zdarzenia na scenie (tj. Ukrywanie) z mojej klasy kontrolera. Więc wszystko, co muszę zrobić, to dodać słuchacza przez

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

ale problem polega na tym, że inicjalizacja zaczyna się zaraz potem

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

a wcześniej

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

zatem .getScene () zwraca null.

Jedynym obejściem, które sam znalazłem, jest dodanie detektora do myPane.sceneProperty (), a gdy nie jest on pusty, otrzymuję scenę, dodaję do niej .windowProperty () my! Cholera! obsługa słuchacza, którą w końcu odzyskuję stage. A wszystko kończy się ustawieniem pożądanych słuchaczy na sceniczne wydarzenia. Myślę, że jest zbyt wielu słuchaczy. Czy to jedyny sposób na rozwiązanie mojego problemu?

Chechulin
źródło

Odpowiedzi:

119

Możesz pobrać instancję kontrolera z FXMLLoaderpo inicjalizacji poprzez getController(), ale musisz wtedy utworzyć instancję FXMLLoaderzamiast używać metod statycznych.

Przeszedłem przez scenę po load()bezpośrednim wywołaniu potem kontrolera:

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
źródło
1
Myślisz, że brakuje ci obsady? Parent root = (Parent) loader.load ();
Paul Eden,
12
Czy to naprawdę najlepszy sposób na zrobienie tego? Czy framework JavaFX nie zapewnia nic, co mogłoby to zarchiwizować?
Hannes
1
Z jakiegoś powodu to nie działa, jeśli używasz konstruktora bez parametrów i podajesz adres URL do load()metody. (Również javadoc na getControllerbrzmi tak, jak powinien setController.)
Bombe,
4
@Bombe Dzieje się tak, ponieważ metoda load () z parametrem URL jest metodą statyczną, która nie ustawia niczego w instancji, do której ją wywołujesz.
zhujik
@Sebastian, ach, rzeczywiście. Mój błąd.
Bombe
112

Wszystko, czego potrzebujesz, to podać AnchorPaneidentyfikator, a następnie możesz go uzyskać Stage.

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

Stąd możesz dodać to Listener, czego potrzebujesz.

Edycja: jak stwierdzono poniżej w EarthMind, nie musi to być AnchorPaneelement; może to być dowolny zdefiniowany przez Ciebie element.

Robert Martin
źródło
2
Krótkie i słodkie. Dzięki
Sedrick,
7
Należy pamiętać, że elementmoże być dowolny element ze związkiem fx:idw tym oknie: Stage stage = (Stage) element.getScene().getWindow();. Na przykład, jeśli masz tylko plikButtonfx:id w oknach z symbolem, użyj go, aby uzyskać scenę.
EarthMind,
29
getScene() wciąż wraca null .
m0skit0
3
To jest odpowiedź, schludnie!
destan
12
Przed zakończeniem inicjalizacji (np. W metodzie inicjalizacji kontrolera) to nie zadziała, ponieważ getScene () zwraca null. Oryginalny plakat sugeruje, że taka jest jego sytuacja. W tym przypadku alternatywa 1 podana przez Utku Özdemir poniżej jest lepsza. Jeśli nie potrzebujesz sceny, wystarczy jeden słuchacz, używam tego w inicjalizacji (), aby rozciągnąć ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie,
32

Wiem, że to nie jest odpowiedź, której chcesz, ale IMO proponowane rozwiązania nie są dobre (i twój własny sposób jest). Czemu? Ponieważ zależą od stanu aplikacji. W JavaFX kontrolka, scena i scena nie zależą od siebie. Oznacza to, że element sterujący może istnieć bez dodawania go do sceny, a scena może istnieć bez dołączenia do sceny. A następnie, w chwili t1, sterowanie może zostać dołączone do sceny, aw chwili t2, scena ta może zostać dodana do sceny (i to wyjaśnia, dlaczego są one wzajemnie obserwowalnymi właściwościami).

Zatem podejście, które sugeruje pobranie odwołania do kontrolera i wywołanie metody, przekazanie do niej etapu, dodaje stan do aplikacji. Oznacza to, że musisz wywołać tę metodę w odpowiednim momencie, zaraz po utworzeniu sceny. Innymi słowy, musisz teraz wykonać zamówienie: 1 - Utwórz etap 2 - Przekaż utworzony etap do sterownika metodą.

Nie możesz (lub nie powinieneś) zmieniać tej kolejności w tym podejściu. Więc straciłeś bezpaństwowość. W oprogramowaniu generalnie stan jest zły. W idealnym przypadku metody nie powinny wymagać żadnej kolejności wywołań.

Więc jakie jest właściwe rozwiązanie? Istnieją dwie możliwości:

1- Twoje podejście, we właściwościach odsłuchu kontrolera, aby dostać się na scenę. Myślę, że to właściwe podejście. Lubię to:

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- Robisz to, co musisz zrobić, gdzie tworzysz Stage(a nie tego chcesz):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
            if (c) {
                System.out.println("I am maximized!");
            }
        });
stage.setScene(someScene);
...
Utku Özdemir
źródło
1
To najkrótsza referencja JavaFX na świecie !! Dzięki, Utku!
Aram Paronikyan
Ciekawe spojrzenie na to. Dzięki :)
Utku Özdemir
Próbuję użyć tego podejścia, aby wypełnić TableView i pokazać wyśrodkowany szacunek ProgressIndicator do TableView, ale okazuje się, że wewnątrz wartości zdarzeń dla getX () i getY () nie są takie same, jak w przypadku użycia po całej inicjalizacji koniec procesu (wewnątrz zdarzenia kliknięcia w przycisku) zarówno dla sceny, jak i sceny, a nawet dla etapu getX () i getY () return NaN, masz jakiś pomysł?
leobelizquierdo
@leobelizquierdo, masz w tej chwili problem z getY (), czy znalazłeś rozwiązanie?
JonasAnon
jeśli dodam panedo sceny, która jest już dołączona do sceny, to nie zadziała, prawda? Czy nie powinno być sprawdzania w scenePropertysłuchaczu i dodawania stagePropertysłuchacza tylko wtedy, gdy scena nadal jest pusta? W przeciwnym razie po prostu zrobić to, co muszę od razu?
jutro
14

Najprostszym sposobem na pobranie obiektu stage w kontrolerze jest:

  1. Dodaj dodatkową metodę we własnej stworzonej klasie kontrolera, np. (Będzie to metoda ustawiająca ustawienie sceny w klasie kontrolera),

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
    
  2. Pobierz kontroler w metodzie startowej i ustaw etap

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    
  3. Teraz możesz uzyskać dostęp do sceny w kontrolerze

Sandeep Kumar
źródło
To był dla mnie zwycięzca. Odpowiedź Roberta Martina na początku zadziałała, ale potem zaczęła rzucać błąd, który rozwiązałem, robiąc stageto w ten sposób.
Dammeul
1

Przypisz fx: id lub zadeklaruj zmienną do / dowolnego węzła: kotwica, przycisk itp. Następnie dodaj do niego moduł obsługi zdarzenia i w ramach tego modułu obsługi zdarzenia wstaw poniższy kod:

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

Mam nadzieję, że to działa dla Ciebie !!

Ankit RajDeo
źródło
1

Platform.runLater działa, aby zapobiec wykonaniu do momentu zakończenia inicjalizacji. W takim przypadku chcę odświeżyć widok listy za każdym razem, gdy zmieniam szerokość okna.

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

w Twoim przypadku

Platform.runLater(()->{
    ((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
});
AdamOutler
źródło
-3

Możesz uzyskać node.getScene, jeśli nie zadzwonisz z Platform.runLater, wynikiem jest wartość null.

przykładowa wartość null:

node.getScene();

przykład brak wartości null:

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
fjqa86
źródło