Pasar los parámetros JavaFX FXML

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

Por ejemplo: el usuario elige un cliente de TableView y se TableView 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).

¿Algunas ideas?

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 marcos adicionales.

Para aplicaciones más grandes y complicadas, valdría la pena investigar si desea utilizar los mecanismos de Dependency Injection o Event Bus dentro de su aplicación.

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

Pase 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.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 ejemplo, es decir, new FXMLLoader(location) . La ubicación es una URL y puedes generar dicha URL desde un recurso de FXML:

 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 de la instancia de su cargador.

Las instancias de FXMLLoader nunca saben nada sobre los objetos de dominio. No pasa directamente objetos de dominio específicos de la aplicación al constructor de FXMLLoader, sino que usted:

  1. Construya un FXMLLoader basado en el marcado de fxml en una ubicación especificada
  2. Obtenga un controlador de la instancia de FXMLLoader.
  3. Invoque métodos en el controlador recuperado para proporcionar al controlador referencias a los objetos de 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 el código, pasando los parámetros que desee de la persona que llama 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 en la definición de fx:controller en FXML, personalmente prefiero obtener el controlador desde el FXMLLoader en lugar de configurar el controlador en el FXMLLoader.

Tener el controlador Recuperar parámetros de un método estático externo

Este método está ejemplificado por la respuesta de Sergey a Javafx 2.0 How-to Application.getParameters () en un archivo Controller.java .

Use Inyección de Dependencia

FXMLLoader admite sistemas de dependency injections como Guice, Spring o Java EE CDI al permitirle configurar una fábrica de controlador personalizada en el FXMLLoader. Esto proporciona una callback que puede usar para crear la instancia del controlador con valores dependientes inyectados por el sistema de dependency injection respectivo. Hay una muestra de integración de FXML con el sistema de dependency injection de Spring (desafortunadamente el enlace está muerto y el contenido se ha ido, si alguien conoce un ejemplo similar, edite esta pregunta para referenciarlo), aunque es un poco más complicado de lo que sería utilizar las nuevas funciones de fábrica de controladores personalizados disponibles en JavaFX 2.2.

Un enfoque de dependency injections realmente bueno y limpio se ejemplifica en el marco de trabajo afterburner.fx con una aplicación de ejemplo de ataques de air que lo usa. afterburner.fx se basa en JEE6 javax.inject para realizar la dependency injection.

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 para la comunicación entre los controladores instanciados de FXML y otra lógica de aplicación.

EventBus es una API de publicación / suscripción simple pero potente con anotaciones que permite que los POJO se comuniquen entre sí en cualquier parte de una JVM sin tener que referirse entre sí.

Q & A de seguimiento

en el primer método, ¿por qué regresas a Stage? El método también puede ser nulo porque usted ya está dando el comando show (); justo antes de la etapa de retorno ;. ¿Cómo planeas el uso al devolver el escenario?

Es una solución funcional a un problema. Se devuelve una etapa de la función showCustomerDialog para que una clase externa pueda almacenar una referencia que desee hacer algo, como ocultar el escenario en función de un clic de botón en la ventana principal, en un momento posterior. Una solución alternativa orientada a objetos podría encapsular la funcionalidad y la referencia de etapa dentro de un objeto CustomerDialog o tener un CustomerDialog extendiendo Stage. Un ejemplo completo para una interfaz orientada a objetos a un diálogo personalizado que encapsula datos de FXML, controlador y modelo está fuera del scope de esta respuesta, pero puede ser una publicación de blog que valga la pena para cualquiera que desee crearla.


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

Ejemplo para la inyección de Dependency de Spring Boot

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 sigue siendo válido y probado en marzo de 2016, en Spring Boot v1.3.3.RELEASE: https://stackoverflow.com/a/36310391/1281217


A veces, es posible que desee pasar los resultados a la persona que llama, en cuyo caso puede verificar la respuesta a la pregunta relacionada:

  • Parámetro JavaFX FXML que pasa del Controlador A a B y viceversa

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

Que podrías usar para agregar tu información al Nodo.

Entonces, puedes llamar a page.setUserData (info);

Y el controlador puede verificar, 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á etiquetada 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, debe asociar un controlador con un nodo y establecer una información de usuario para el nodo.

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

       

Definir el valor del External Text para la variable del espacio de nombre 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(); } } 

Esto funciona ..

Recuerde que la primera vez que imprima el valor de aprobación obtendrá nulo. Puede usarlo después de que cargue 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.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()); } 

Me doy cuenta de que esta es una publicación muy antigua y que ya tiene algunas respuestas excelentes, pero quería hacer un MCVE simple para demostrar uno de estos enfoques y permitirles 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 utilizado para iniciar la aplicación y llamar al primer controlador.
  2. Controller1.java – El controlador para el primer diseño de FXML.
  3. Controller2.java – El controlador para el segundo diseño de FXML.
  4. Layout1.fxml : el diseño de FXML para la primera escena.
  5. Layout2.fxml : el diseño de FXML para la segunda escena.

Todos los archivos se enumeran en su totalidad en la parte inferior de esta publicación.

El objective: demostrar los valores de paso de Controller1 a Controller2 y viceversa.

El flujo del progtwig:

  • La primera escena contiene un TextField , un Button y una Label . Cuando se hace clic en el Button , se carga y se visualiza 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 TextField 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 debe hacer que el concepto sea muy claro.

El código en sí 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:

                        

Layout2.fxml:

                        

Tienes que crear una clase de contexto.

 public class Context { private final static Context instance = new Context(); public static Context getInstance() { return instance; } private Connection con; public void setConnection(Connection con) { this.con=con; } public Connection getConnection() { return con; } private TabRoughController tabRough; public void setTabRough(TabRoughController tabRough) { this.tabRough=tabRough; } public TabRoughController getTabRough() { return tabRough; } } 

Solo tiene que establecer la instancia del controlador en la inicialización usando

 Context.getInstance().setTabRough(this); 

y puedes usarlo desde toda tu aplicación solo usando

 TabRoughController cont=Context.getInstance().getTabRough(); 

Ahora puede pasar el parámetro a cualquier controlador desde la aplicación completa.

Aquí hay un ejemplo para usar un controlador inyectado por Guice.

 /** * Loads a FXML file and injects its controller from the given Guice {@code Provider} */ public abstract class GuiceFxmlLoader { public GuiceFxmlLoader(Stage stage, Provider provider) { mStage = Objects.requireNonNull(stage); mProvider = Objects.requireNonNull(provider); } /** * @return the FXML file name */ public abstract String getFileName(); /** * Load FXML, set its controller with given {@code Provider}, and add it to {@code Stage}. */ public void loadView() { try { FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(getFileName())); loader.setControllerFactory(p -> mProvider.get()); Node view = loader.load(); setViewInStage(view); } catch (IOException ex) { LOGGER.error("Failed to load FXML: " + getFileName(), ex); } } private void setViewInStage(Node view) { BorderPane pane = (BorderPane)mStage.getScene().getRoot(); pane.setCenter(view); } private static final Logger LOGGER = Logger.getLogger(GuiceFxmlLoader.class); private final Stage mStage; private final Provider mProvider; } 

Aquí hay una implementación concreta del cargador:

 public class ConcreteViewLoader extends GuiceFxmlLoader { @Inject public ConcreteViewLoader(Stage stage, Provider provider) { super(stage, provider); } @Override public String getFileName() { return "my_view.fxml"; } } 

Tenga en cuenta que este ejemplo carga la vista en el centro de un BoarderPane que es la raíz de la escena en el escenario. Esto es irrelevante para el ejemplo (detalles de implementación de mi caso de uso específico) pero decidí dejarlo tal como algunos lo consideren útil.

Puede decidir usar una lista pública observable para almacenar datos públicos, o simplemente crear un método de establecimiento público para almacenar datos y recuperarlos del controlador correspondiente