JavaFX: ¿Cómo obtener el escenario del controlador durante la inicialización?

91

Quiero manejar eventos de escenario (es decir, ocultar) de mi clase de controlador. Entonces todo lo que tengo que hacer es agregar un oyente a través de

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

pero el problema es que la inicialización comienza justo después

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

y antes

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

por tanto .getScene () devuelve nulo.

La única solución que encontré por mí mismo es agregar un oyente a myPane.sceneProperty (), y cuando no se vuelve nulo, obtengo una escena, agrego a su .windowProperty () ¡mi! Maldita sea! manejo del oyente que finalmente recupero el escenario. Y todo termina con la puesta en escena de los oyentes deseados. Creo que hay demasiados oyentes. ¿Es la única forma de resolver mi problema?

Chechulin
fuente

Respuestas:

119

Puede obtener la instancia del controlador FXMLLoaderdespués de la inicialización a través de getController(), pero debe crear una instancia en FXMLLoaderlugar de usar los métodos estáticos.

Pasaría el escenario después de llamar load()directamente al controlador después:

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
fuente
1
¿Crees que te falta un yeso? Raíz principal = (principal) loader.load ();
Paul Eden
12
¿Es esta realmente la mejor manera de hacer esto? ¿No hay nada proporcionado por el marco JavaFX para archivar esto?
Hannes
1
Por alguna razón, esto no funciona si usa el constructor sin parámetros y entrega la URL al load()método. (Además, el javadoc encendido getControllerparece que debería estar encendido setController).
Bombe
4
@Bombe esto se debe a que el método load () con el parámetro URL es un método estático que no establece nada en la instancia en la que lo está llamando.
zhujik
@Sebastian, ay, de hecho. Culpa mía.
Bombe
112

Todo lo que necesita es darle AnchorPaneuna identificación, y luego puede obtenerla Stage.

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

Desde aquí, puede agregar en el Listener que necesite.

Editar: Como indica EarthMind a continuación, no tiene por qué ser el AnchorPaneelemento; puede ser cualquier elemento que haya definido.

Robert Martin
fuente
2
Corto y dulce. Gracias
Sedrick
7
Tenga en cuenta que elementpuede ser cualquier elemento con un fx:iden esa ventana: Stage stage = (Stage) element.getScene().getWindow();. Por ejemplo, si solo tiene un Buttoncon un fx:iden sus ventanas, utilícelo para subir al escenario.
EarthMind
29
getScene()todavía regresa null.
m0skit0
3
¡Esta es la respuesta, genial!
destan
12
Antes de que se complete la inicialización (por ejemplo, en el método de inicialización del controlador), esto no funcionará porque getScene () devuelve nulo. El cartel original implica que esta es su situación. En este caso, la alternativa 1 dada por Utku Özdemir a continuación es mejor. Si no necesita el escenario, entonces un oyente es suficiente, lo uso en initialize () para hacer un estiramiento de ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Georgie
32

Sé que no es la respuesta que desea, pero en mi opinión, las soluciones propuestas no son buenas (y a su manera sí lo es). ¿Por qué? Porque dependen del estado de la aplicación. En JavaFX, un control, una escena y un escenario no dependen el uno del otro. Esto significa que un control puede vivir sin ser agregado a una escena y una escena puede existir sin estar adjunta a un escenario. Y luego, en un instante de tiempo t1, el control se puede adjuntar a una escena y en el instante t2, esa escena se puede agregar a un escenario (y eso explica por qué son propiedades observables entre sí).

Entonces, el enfoque que sugiere obtener la referencia del controlador e invocar un método, pasando la etapa, agrega un estado a su aplicación. Esto significa que debe invocar ese método en el momento adecuado, justo después de que se crea la etapa. En otras palabras, debe seguir una orden ahora: 1- Crear la etapa 2- Pasar esta etapa creada al controlador a través de un método.

No puede (o no debe) cambiar este orden en este enfoque. Entonces perdiste la apatridia. Y en el software, en general, el estado es malo. Idealmente, los métodos no deberían requerir ningún orden de llamada.

Entonces, ¿cuál es la solución correcta? Hay dos alternativas:

1- Tu enfoque, en las propiedades de escucha del controlador para subir al escenario. Creo que este es el enfoque correcto. Me gusta esto:

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- Haces lo que tienes que hacer donde creas el Stage(y eso no es lo que quieres):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
            if (c) {
                System.out.println("I am maximized!");
            }
        });
stage.setScene(someScene);
...
Utku Özdemir
fuente
1
¡Esta es la referencia JavaFX más corta del planeta! ¡Gracias, Utku!
Aram Paronikyan
Interesante forma de verlo. Gracias :)
Utku Özdemir
Estoy tratando de usar este enfoque para poblar un TableView y mostrar un ProgressIndicator centrado en el TableView, pero resulta que dentro de los eventos los valores para getX () y getY () no son los mismos que si usa después de toda la inicialización process end (dentro de un evento de clic en un botón) ya sea para la escena y el escenario, incluso el escenario getX () y getY () devuelven NaN, ¿alguna idea?
leobelizquierdo
@leobelizquierdo, tengo ese problema getY () en este momento, ¿encontraste una solución?
JonasAnon
si agrego panea una escena que ya está adjunta a un escenario, esto no funcionará, ¿verdad? ¿No debería haber una verificación en el scenePropertyoyente y solo agregar el stagePropertyoyente si la etapa aún es nula? De lo contrario, ¿hago lo que necesito hacer de inmediato?
mañana
14

La forma más sencilla de obtener el objeto de escenario en el controlador es:

  1. Agregue un método adicional en la clase de controlador creada por sí mismo como (será un método de establecimiento para configurar el escenario en la clase de controlador),

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
    
  2. Obtenga el controlador en el método de inicio y configure el escenario

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    
  3. Ahora puedes acceder al escenario en el controlador.

Sandeep Kumar
fuente
Este fue el ganador para mí. La respuesta de Robert Martin funcionó al principio, pero luego comenzó a arrojar un error que resolví de stageesta manera.
Dammeul
1

Asigne fx: id o declare variable a / de cualquier nodo: anchorpane, button, etc. Luego agregue el controlador de eventos y dentro de ese controlador de eventos inserte el código dado a continuación:

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

¡¡Espero que esto funcione para usted!!

Ankit RajDeo
fuente
1

Platform.runLater trabaja para evitar la ejecución hasta que se completa la inicialización. En este caso, quiero actualizar una vista de lista cada vez que cambio el tamaño del ancho de la ventana.

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

en tu caso

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

Puedes quedarte node.getScene, si no llamas desdePlatform.runLater , el resultado es un valor nulo.

ejemplo de valor nulo:

node.getScene();

ejemplo sin valor nulo:

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