Pasando los parámetros JavaFX FXML

javafx parameters dependency-injection parameter-passing fxml


¿Cómo puedo pasar los parámetros a una ventana secundaria en javafx? ¿Hay alguna forma de comunicarse con el controlador correspondiente?

Por ejemplo: el usuario elige un cliente de TableView y se abre una nueva ventana que muestra la información del cliente.

Stage newStage = new Stage();
try 
{
    AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
    Scene scene = new Scene(page);
    newStage.setScene(scene);
    newStage.setTitle(windowTitle);
    newStage.setResizable(isResizable);
    if(showRightAway) 
    {
        newStage.show();
    }
}

newStage sería la nueva ventana. El problema es que no puedo encontrar una manera de decirle al controlador dónde buscar la información del cliente (pasando el id como parámetro).

¿Alguna idea?





Answer 1 jewelsea


Enfoque recomendado

Esta respuesta enumera diferentes mecanismos para pasar parámetros a los controladores FXML.

Para aplicaciones pequeñas recomiendo pasar los parámetros directamente de la persona que llama al controlador-es simple,directo y no requiere de marcos adicionales.

Para aplicaciones más grandes y complicadas, valdría la pena investigar si desea utilizar mecanismos de inyección de dependencia o bus de eventos dentro de su aplicación.

Pasando parámetros directamente de la persona que llama al controlador

Pasar los datos personalizados a un controlador FXML recuperando el controlador de la instancia del cargador FXML y llamando a un método en el controlador para inicializarlo con los valores de datos requeridos.

Algo como el siguiente código:

public Stage showCustomerDialog(Customer customer) {
  FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
      "customerDialog.fxml"
    )
  );

  Stage stage = new Stage(StageStyle.DECORATED);
  stage.setScene(
    new Scene(
      (Pane) loader.load()
    )
  );

  CustomerDialogController controller = 
    loader.<CustomerDialogController>getController();
  controller.initData(customer);

  stage.show();

  return stage;
}

...

class CustomerDialogController {
  @FXML private Label customerName;
  void initialize() {}
  void initData(Customer customer) {
    customerName.setText(customer.getName());
  }
}

Se construye un nuevo FXMLLoader como se muestra en el código de muestra, es decir, un new FXMLLoader(location) . La ubicación es una URL y puede generar dicha URL a partir de un recurso FXML al:

new FXMLLoader(getClass().getResource("sample.fxml"));

Tenga cuidado de NO utilizar una función de carga estática en el FXMLLoader, o no podrá obtener su controlador desde la instancia del cargador.

Las instancias de FXMLLoader por sí mismas nunca saben nada sobre los objetos del dominio.No pasas directamente los objetos de dominio específicos de la aplicación al constructor del FXMLLoader,sino a ti:

  1. Construye un FXMLLoader basado en el marcado fxml en un lugar específico
  2. Consigue un controlador de la instancia FXMLLoader.
  3. Invocar métodos en el controlador recuperado para proporcionar al controlador referencias a los objetos del dominio.

Este blog (de otro escritor) proporciona un ejemplo alternativo, pero similar.

Configuración de un controlador en el FXMLLoader

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
        "customerDialog.fxml"
    )
);
loader.setController(dialogController);

Pane mainPane = (Pane) loader.load();

Puede construir un nuevo controlador en código, pasando los parámetros que desee de su llamador al constructor del controlador. Una vez que haya construido un controlador, puede configurarlo en una instancia de FXMLLoader antes de invocar el método de instancia load() .

Para configurar un controlador en un cargador (en JavaFX 2.x) NO PUEDE definir un atributo fx:controller en su archivo fxml.

Debido a la limitación de la definición del fx:controller en FXML, personalmente prefiero obtener el controlador del FXMLLoader en lugar de configurar el controlador en el FXMLLoader.

Hacer que el controlador recupere los parámetros de un método estático externo

Este método se ejemplifica con la respuesta de Sergey a Javafx 2.0 How-to Application.getParameters () en un archivo Controller.java .

Usar inyección de dependencia

FXMLLoader soporta sistemas de inyección de dependencia como Guice,Spring o Java EE CDI,permitiéndole establecer una fábrica de controladores personalizados en el FXMLLoader.Esto proporciona una llamada de retorno que puede utilizar para crear la instancia del controlador con los valores de dependencia inyectados por el sistema de inyección de dependencia respectivo.

Un ejemplo de la aplicación JavaFX y la inyección de dependencia del controlador con Spring se proporciona en la respuesta a:

Un marco de inyección de dependencia realmente agradable y limpio se ejemplifica en el marco afterburner.fx con una aplicación de muestra de ataques de aire que lo utiliza. afterburner.fx se basa en JEE6 javax.inject para realizar la inyección de dependencia.

Use un bus de eventos

Greg Brown, el creador e implementador de la especificación FXML original, a menudo sugiere considerar el uso de un bus de eventos, como el EventBus de Guava, para la comunicación entre controladores instanciados FXML y otra lógica de aplicación.

El EventBus es una simple pero poderosa API de suscripción de público con anotaciones que permite a los POJOs comunicarse entre sí en cualquier lugar de una JVM sin tener que referirse unos a otros.

Preguntas y respuestas de seguimiento

en el primer método,¿por qué regresas a Stage? El método también puede ser nulo porque ya has dado el comando show();justo antes de retornar a Stage;.¿Cómo planeas el uso devolviendo a Stage

Es una solución funcional a un problema. Se devuelve un escenario desde la función showCustomerDialog para que una clase externa pueda almacenar una referencia al mismo que desee hacer algo, como ocultar el escenario con un clic en la ventana principal, más adelante. Una solución alternativa orientada a objetos podría encapsular la funcionalidad y la referencia de etapa dentro de un objeto CustomerDialog o hacer que CustomerDialog extienda Stage. Un ejemplo completo de una interfaz orientada a objetos para un diálogo personalizado que encapsula FXML, el controlador y los datos del modelo está más allá del alcance de esta respuesta, pero puede ser una publicación de blog que valga la pena para cualquiera que esté dispuesto a crear una.


Información adicional proporcionada por el usuario de StackOverflow llamado @dzim

Ejemplo de inyección de dependencia de arranque de resorte

La cuestión de cómo hacerlo "The Spring Boot Way", hubo una discusión sobre JavaFX 2, que respondí en el enlace permanente adjunto. El enfoque aún es válido y se probó en marzo de 2016, en Spring Boot v1.3.3. PUBLICACIÓN: https://stackoverflow.com/a/36310391/1281217


A veces,puede que quieras pasar los resultados a la persona que llama,en cuyo caso puedes comprobar la respuesta a la pregunta relacionada:




Answer 2 Zephyr


Me doy cuenta de que este es un post muy antiguo y que ya tiene algunas grandes respuestas,pero quería hacer un simple MCVE para demostrar un enfoque de este tipo y permitir a los nuevos codificadores una forma de ver rápidamente el concepto en acción.

En este ejemplo,usaremos 5 archivos:

  1. Main.java : simplemente se usa para iniciar la aplicación y llamar al primer controlador.
  2. Controller1.java : el controlador para el primer diseño FXML.
  3. Controller2.java : el controlador para el segundo diseño FXML.
  4. Layout1.fxml : el diseño FXML para la primera escena.
  5. Layout2.fxml : el diseño FXML para la segunda escena.

Todos los archivos se enumeran en su totalidad al final de esta publicación.

El objetivo: demostrar los valores de paso del Controller1 al Controller2 y viceversa.

El flujo del programa:

  • La primera escena contiene un TextField , un Button y una Label . Cuando se hace clic en el Button , se carga y se muestra la segunda ventana, incluido el texto ingresado en el TextField .
  • Dentro de la segunda escena, también hay un TextField , un Button y una Label . La Label mostrará el texto ingresado en el TextField en la primera escena.
  • Al ingresar texto en el TextField de la segunda escena y hacer clic en su Button , la Label la primera escena se actualiza para mostrar el texto ingresado.

Esta es una demostración muy simple y seguramente podría significar alguna mejora,pero debería dejar el concepto muy claro.

El código en sí mismo también se comenta con algunos detalles de lo que está sucediendo y cómo.

EL CÓDIGO

Main.java:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Create the first controller, which loads Layout1.fxml within its own constructor
        Controller1 controller1 = new Controller1();

        // Show the new stage
        controller1.showStage();

    }
}

Controller1.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller1 {

    // Holds this controller's Stage
    private final Stage thisStage;

    // Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
    @FXML
    private TextField txtToSecondController;
    @FXML
    private Button btnOpenLayout2;
    @FXML
    private Label lblFromController2;

    public Controller1() {

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout1");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    /**
     * The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
     */
    @FXML
    private void initialize() {

        // Add an action for the "Open Layout2" button
        btnOpenLayout2.setOnAction(event -> openLayout2());
    }

    /**
     * Performs the action of loading and showing Layout2
     */
    private void openLayout2() {

        // Create the second controller, which loads its own FXML file. We pass a reference to this controller
        // using the keyword [this]; that allows the second controller to access the methods contained in here.
        Controller2 controller2 = new Controller2(this);

        // Show the new stage/window
        controller2.showStage();

    }

    /**
     * Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
     */
    public String getEnteredText() {
        return txtToSecondController.getText();
    }

    /**
     * Allows other controllers to set the text of this layout's Label
     */
    public void setTextFromController2(String text) {
        lblFromController2.setText(text);
    }
}

Controller2.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller2 {

    // Holds this controller's Stage
    private Stage thisStage;

    // Will hold a reference to the first controller, allowing us to access the methods found there.
    private final Controller1 controller1;

    // Add references to the controls in Layout2.fxml
    @FXML
    private Label lblFromController1;
    @FXML
    private TextField txtToFirstController;
    @FXML
    private Button btnSetLayout1Text;

    public Controller2(Controller1 controller1) {
        // We received the first controller, now let's make it usable throughout this controller.
        this.controller1 = controller1;

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout2");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    @FXML
    private void initialize() {

        // Set the label to whatever the text entered on Layout1 is
        lblFromController1.setText(controller1.getEnteredText());

        // Set the action for the button
        btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
    }

    /**
     * Calls the "setTextFromController2()" method on the first controller to update its Label
     */
    private void setTextOnLayout1() {
        controller1.setTextFromController2(txtToFirstController.getText());
    }

}

Layout1.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToSecondController"/>
            <Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
        </HBox>
        <VBox alignment="CENTER">
            <Label text="Text From Controller2:"/>
            <Label fx:id="lblFromController2" text="Nothing Yet!"/>
        </VBox>
    </VBox>
</AnchorPane>

Layout2.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
        <VBox alignment="CENTER">
            <Label text="Text From Controller1:"/>
            <Label fx:id="lblFromController1" text="Nothing Yet!"/>
        </VBox>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToFirstController"/>
            <Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
        </HBox>
    </VBox>
</AnchorPane>



Answer 3 Alexander Kirov


La clase javafx.scene.Node tiene un par de métodos setUserData(Object)y Object getUserData()

Que podrías usar para añadir tu información al Nodo.

Así que puedes llamar a page.setUserData(info);

Y el controlador puede comprobar,si la información está configurada.Además,puede usar ObjectProperty para la transferencia de datos hacia atrás,si es necesario.

Observe una documentación aquí: http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html Antes de la frase "En la primera versión, handleButtonAction () está etiquetado con @FXML para permitir el marcado definido en el documento del controlador para invocarlo. En el segundo ejemplo, el campo del botón se anota para permitir que el cargador establezca su valor. El método initialize () se anota de manera similar ".

Por lo tanto,necesitas asociar un controlador con un nodo,y establecer los datos de un usuario en el nodo.




Answer 4 user1503636


Aquí hay un ejemplo para pasar parámetros a un documento fxml a través de un espacio de nombres.

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
    <BorderPane>
        <center>
            <Label text="$labelText"/>
        </center>
    </BorderPane>
</VBox>

Definir valor External Text para la variable de espacio de nombres labelText :

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class NamespaceParameterExampleApplication extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));

        fxmlLoader.getNamespace()
                  .put("labelText", "External Text");

        final Parent root = fxmlLoader.load();

        primaryStage.setTitle("Namespace Parameter Example");
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }
}



Answer 5 diego matos - keke


Esto funciona ..

Recuerde que la primera vez que imprima el valor de aprobación obtendrá un valor nulo. Puede usarlo después de cargar sus ventanas, lo mismo para todo lo que desee codificar para cualquier otro componente.

Primer controlador

try {
    Stage st = new Stage();
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));

    Parent sceneMain = loader.load();

    MainOnlineController controller = loader.<MainOnlineController>getController();
    controller.initVariable(99L);

    Scene scene = new Scene(sceneMain);
    st.setScene(scene);
    st.setMaximized(true);
    st.setTitle("My App");
    st.show();
} catch (IOException ex) {
    Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
}

Otro controlador

public void initVariable(Long id_usuario){
    this.id_usuario = id_usuario;
    label_usuario_nombre.setText(id_usuario.toString());
}