JavaFX: Como obter o estágio do controlador durante a inicialização?

89

Eu quero lidar com eventos de estágio (ou seja, esconder) da minha classe de controlador. Então, tudo o que tenho a fazer é adicionar um ouvinte via

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

mas o problema é que a inicialização começa logo após

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

e antes

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

portanto .getScene () retorna nulo.

A única solução alternativa que encontrei por mim mesmo é adicionar um ouvinte a myPane.sceneProperty () e, quando ele se tornar nulo, eu obtenho a cena, acrescente a ele .windowProperty () meu! Goddamn! ouvinte manipulação que finalmente recupero estágio. E tudo termina com a definição dos ouvintes desejados para organizar eventos. Acho que há muitos ouvintes. É a única maneira de resolver meu problema?

Chechulin
fonte

Respostas:

116

Você pode obter a instância do controlador FXMLLoaderapós a inicialização via getController(), mas precisa instanciar um em FXMLLoadervez de usar os métodos estáticos.

Eu passaria o estágio depois de chamar load()diretamente para o controlador:

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
fonte
1
Acha que está faltando um gesso? Raiz pai = (Pai) loader.load ();
Paul Eden
12
Esta é realmente a melhor maneira de fazer isso? Não há nada fornecido pela estrutura JavaFX para arquivar isso?
Hannes,
1
Por algum motivo, isso não funcionará se você usar o construtor sem parâmetros e fornecer a URL para o load()método. (Além disso, o javadoc em getControllerparece que deveria estar setController.)
Bombe
4
@Bombe isso ocorre porque o método load () com o parâmetro URL é um método estático que não define nada na instância que você está chamando.
zhujik
@Sebastian, ouch, de fato. Foi mal.
Bombe
109

Tudo que você precisa é fornecer AnchorPaneum ID, e então você pode obtê-lo a Stagepartir dele.

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

A partir daqui, você pode adicionar o Listenerque você precisa.

Editar: conforme declarado pelo EarthMind abaixo, não precisa ser o AnchorPaneelemento; pode ser qualquer elemento que você definiu.

Robert Martin
fonte
2
Curto e grosso. Obrigado
Sedrick
7
Note-se que elementpode ser qualquer elemento com um fx:idnessa janela: Stage stage = (Stage) element.getScene().getWindow();. Por exemplo, se você tiver apenas um Buttoncom um fx:idem suas janelas, use-o para obter o palco.
EarthMind
28
getScene()ainda retorna null.
m0skit0
3
Esta é a resposta, legal!
destino
12
Antes da inicialização ser concluída (por exemplo, no método de inicialização do controlador), isso não funcionará porque getScene () retorna nulo. O pôster original sugere que esta é a situação dele. Neste caso, a alternativa 1 fornecida por Utku Özdemir abaixo é melhor. Se você não precisa do palco, um ouvinte é suficiente. Eu uso isso em initialize () para fazer um alongamento de ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie
32

Eu sei que não é a resposta que você quer, mas IMO as soluções propostas não são boas (e o seu próprio jeito é). Por quê? Porque dependem do estado do aplicativo. No JavaFX, um controle, uma cena e um palco não dependem um do outro. Isso significa que um controle pode viver sem ser adicionado a uma cena e uma cena pode existir sem ser anexada a um palco. E então, em um instante t1, o controle pode ser anexado a uma cena e no instante t2, essa cena pode ser adicionada a um palco (e isso explica por que são propriedades observáveis ​​uma da outra).

Portanto, a abordagem que sugere obter a referência do controlador e invocar um método, passando o estágio para ele, adiciona um estado à sua aplicação. Isso significa que você precisa invocar esse método no momento certo, logo após a criação do estágio. Em outras palavras, você precisa seguir uma ordem agora: 1- Crie a etapa 2- Passe esta etapa criada para o controlador por meio de um método.

Você não pode (ou não deve) alterar esta ordem nesta abordagem. Então você perdeu a apatridia. E em software, geralmente, o estado é mau. Idealmente, os métodos não devem exigir nenhuma ordem de chamada.

Então, qual é a solução certa? Existem duas alternativas:

1- Sua abordagem, nas propriedades de escuta do controlador para entrar no palco. Acho que essa é a abordagem certa. Como isso:

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- Você faz o que precisa onde cria o Stage(e não é isso que você quer):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
            if (c) {
                System.out.println("I am maximized!");
            }
        });
stage.setScene(someScene);
...
Utku Özdemir
fonte
1
Esta é a referência JavaFX mais curta do planeta !! Obrigado, Utku!
Aram Paronikyan
Maneira interessante de ver isso. Obrigado :)
Utku Özdemir
Estou tentando usar essa abordagem para preencher uma TableView e mostrar um ProgressIndicator centrado em relação à TableView, mas descobri que dentro dos eventos os valores para getX () e getY () não são os mesmos que se você usar depois de toda a inicialização fim do processo (dentro de um evento click em um botão) tanto para cena quanto para palco, até mesmo stage getX () e getY () return NaN, alguma ideia?
leobelizquierdo
@leobelizquierdo, tenho aquele problema de getY () neste momento, encontrou uma solução?
JonasAnon
se eu adicionar panea uma cena que já está anexada a um palco, isso não vai funcionar, certo? Não deveria haver uma verificação no scenePropertyouvinte e apenas adicionar o stagePropertyouvinte se o estágio ainda for nulo? Caso contrário, faço o que preciso imediatamente?
amanhã
14

A maneira mais simples de obter o objeto de estágio no controlador é:

  1. Adicione um método extra na própria classe de controlador criada como (será um método setter para definir o estágio na classe de controlador),

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
    
  2. Coloque o controlador no método de início e defina o estágio

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    
  3. Agora você pode acessar o estágio no controlador

Sandeep Kumar
fonte
Este foi o vencedor para mim. A resposta de Robert Martin funcionou no início, mas depois começou a apresentar um erro que resolvi ficando stageassim.
Dammeul
1

Atribua fx: id ou declare a variável para / de qualquer nó: âncora, botão, etc. Em seguida, adicione o manipulador de eventos a ele e, dentro desse manipulador de eventos, insira o código fornecido abaixo:

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

Espero que funcione para voce!!

Ankit RajDeo
fonte
1

Platform.runLater funciona para evitar a execução até que a inicialização seja concluída. Nesse caso, quero atualizar a visualização de uma lista sempre que redimensiono a largura da janela.

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

no seu caso

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

Você pode obter com node.getScene, se você não ligar de Platform.runLater, o resultado é um valor nulo.

exemplo de valor nulo:

node.getScene();

exemplo sem valor nulo:

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