Passando Parametri JavaFX FXML

Come posso passare i parametri a una finestra secondaria in javafx? C’è un modo per comunicare con il controller corrispondente?

Ad esempio: l’utente sceglie un cliente da un TableView e viene aperta una nuova finestra, che mostra le informazioni 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 sarebbe la nuova finestra. Il problema è che non riesco a trovare un modo per dire al controller dove cercare le informazioni del cliente (passando l’id come parametro).

Qualche idea?

    Approccio consigliato

    Questa risposta enumera diversi meccanismi per il passaggio dei parametri ai controller FXML.

    Per le piccole applicazioni consiglio vivamente di passare i parametri direttamente dal chiamante al controller: è semplice, diretto e non richiede quadri aggiuntivi.

    Per applicazioni più grandi e più complicate, vale la pena di indagare se si desidera utilizzare i meccanismi Iniezione delle dipendenze o Bus eventi all’interno dell’applicazione.

    Passare i parametri direttamente dal chiamante al controller

    Passare dati personalizzati a un controller FXML recuperando il controller dall’istanza del caricatore FXML e chiamando un metodo sul controller per inizializzarlo con i valori dei dati richiesti.

    Qualcosa come il seguente codice:

     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()); } } 

    Un nuovo FXMLLoader è costruito come mostrato nel codice di esempio, ad esempio il new FXMLLoader(location) . La posizione è un URL ed è ansible generare tale URL da una risorsa FXML tramite:

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

    Fare attenzione a NON utilizzare una funzione di caricamento statico su FXMLLoader, altrimenti non sarà ansible ottenere il controller dall’istanza del caricatore.

    Le istanze FXMLLoader non sanno mai nulla sugli oggetti di dominio. Non passi direttamente oggetti di dominio specifici dell’applicazione nel costruttore FXMLLoader, invece tu:

    1. Costruisci un FXMLLoader basato sul markup fxml in una posizione specificata
    2. Ottieni un controller dall’istanza FXMLLoader.
    3. Richiamare i metodi sul controller recuperato per fornire al controllore i riferimenti agli oggetti del dominio.

    Questo blog (di un altro autore) fornisce un esempio alternativo, ma simile.

    Impostazione di un controller su FXMLLoader

     CustomerDialogController dialogController = new CustomerDialogController(param1, param2); FXMLLoader loader = new FXMLLoader( getClass().getResource( "customerDialog.fxml" ) ); loader.setController(dialogController); Pane mainPane = (Pane) loader.load(); 

    Puoi build un nuovo controller nel codice, passando tutti i parametri che vuoi dal tuo chiamante nel costruttore del controller. Una volta costruito un controller, è ansible impostarlo su un’istanza FXMLLoader prima di richiamare il metodo di istanza load() .

    Per impostare un controller su un caricatore (in JavaFX 2.x) NON è inoltre ansible definire un attributo fx:controller nel file fxml.

    A causa della limitazione della definizione del fx:controller in FXML, personalmente preferisco prendere il controller dal FXMLLoader piuttosto che impostare il controller nel FXMLLoader.

    Avere il controller Recupera parametri da un metodo statico esterno

    Questo metodo è esemplificato dalla risposta di Sergey a Javafx 2.0 How-to Application.getParameters () in un file Controller.java .

    Utilizzare l’iniezione di dipendenza

    FXMLLoader supporta sistemi di iniezione dipendenti come Guice, Spring o Java EE CDI consentendo di impostare una factory controller personalizzata su FXMLLoader. Questo fornisce un callback che è ansible utilizzare per creare l’istanza del controller con valori dipendenti iniettati dal rispettivo sistema di iniezione delle dipendenze. C’è un esempio di integrazione di FXML con il sistema di iniezione di dipendenza Spring (sfortunatamente il collegamento è morto e il contenuto è scomparso, se qualcuno conosce un esempio simile, per favore modifica questa domanda per farvi riferimento), anche se è un po ‘più clou di quanto non sarebbe utilizzare le nuove funzionalità di fabbrica del controller personalizzato rese disponibili in JavaFX 2.2.

    Un approccio di iniezione delle dipendenze davvero bello e pulito è esemplificato dal framework afterburner.fx con un’applicazione air-hacks di esempio che la usa. afterburner.fx si basa su JEE6 javax.inject per eseguire l’iniezione delle dipendenze.

    Utilizzare un bus eventi

    Greg Brown, il creatore e l’implementatore delle specifiche FXML originali, suggerisce spesso l’utilizzo di un bus eventi per la comunicazione tra i controller istanziati FXML e altre logiche applicative.

    EventBus è un’API di pubblicazione / sottoscrizione semplice ma potente con annotazioni che consente ai POJO di comunicare tra loro in qualsiasi posizione in una JVM senza dover fare riferimento l’uno all’altro.

    Domande e risposte sul follow-up

    sul primo metodo, perché torni Stage? Anche il metodo può essere vuoto perché stai già dando il comando show (); poco prima della fase di ritorno ;. Come pianificare l’utilizzo restituendo lo stage

    È una soluzione funzionale a un problema. Uno stage viene restituito dalla funzione showCustomerDialog modo che un riferimento ad esso possa essere memorizzato da una class esterna che potrebbe voler fare qualcosa, ad esempio hide lo stage in base a un clic del pulsante nella finestra principale, in un secondo momento. Una soluzione alternativa orientata agli oggetti potrebbe incapsulare la funzionalità e il riferimento dello stage all’interno di un object CustomerDialog o fare in modo che StageDialog estenda lo stage. Un esempio completo per un’interfaccia orientata agli oggetti a una finestra di dialogo personalizzata che incapsula dati FXML, controller e modello va oltre lo scopo di questa risposta, ma può costituire un post di blog utile per chiunque sia incline a crearne uno.


    Informazioni aggiuntive fornite dall’utente StackOverflow denominato @dzim

    Esempio di iniezione delle dipendenze dello Spring Boot

    La domanda su come farlo “The Spring Boot Way”, c’è stata una discussione su JavaFX 2, che ho ascoltato nel permalink allegato. L’approccio è ancora valido e testato a marzo 2016, su Spring Boot v1.3.3.RELEASE: https://stackoverflow.com/a/36310391/1281217


    A volte, potresti voler restituire i risultati al chiamante, nel qual caso puoi verificare la risposta alla domanda correlata:

    • JavaFX FXML Parametro che passa dal controller A a B e viceversa

    La class javafx.scene.Node ha una coppia di metodi setUserData (Object) e Object getUserData ()

    Che potresti usare per aggiungere le tue informazioni al Nodo.

    Quindi, puoi chiamare page.setUserData (informazioni);

    E il controller può controllare, se le informazioni sono impostate. Inoltre, è ansible utilizzare ObjectProperty per il trasferimento dei dati back-forward, se necessario.

    Osservare una documentazione qui: http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html Prima della frase “Nella prima versione, il handleButtonAction () è etichettato con @FXML per consentire il markup definito nel documento del controller per invocarlo.Nel secondo esempio, il campo del pulsante è annotato per consentire al caricatore di impostare il suo valore.Il metodo initialize () è analogamente annotato. ”

    Pertanto, è necessario associare un controller a un nodo e impostare un dato utente sul nodo.

    Ecco un esempio per il passaggio di parametri a un documento fxml attraverso lo spazio dei nomi.

     < ?xml version="1.0" encoding="UTF-8"?> < ?import javafx.scene.control.Label?> < ?import javafx.scene.layout.BorderPane?> < ?import javafx.scene.layout.VBox?>   

    Definisci il valore External Text per la variabile della variabile namespaceText:

     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(); } } 

    Questo FUNZIONA ..

    Ricorda che per la prima volta che stampi il valore che passa otterrai nullo, puoi usarlo dopo aver caricato le windows, lo stesso per tutto ciò che vuoi codificare per qualsiasi altro componente.

    Primo controller

     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); } 

    Un altro controller

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

    Mi rendo conto che questo è un post molto vecchio e ha già alcune grandi risposte, ma volevo fare un semplice MCVE per dimostrare un tale approccio e consentire ai nuovi programmatori un modo per vedere rapidamente il concetto in azione.

    In questo esempio, utilizzeremo 5 file:

    1. Main.java – Semplicemente usato per avviare l’applicazione e chiamare il primo controller.
    2. Controller1.java – Il controller per il primo layout FXML.
    3. Controller2.java – Il controller per il secondo layout FXML.
    4. Layout1.fxml – Il layout FXML per la prima scena.
    5. Layout2.fxml – Il layout FXML per la seconda scena.

    Tutti i file sono elencati nella loro interezza in fondo a questo post.

    L’objective: dimostrare i valori di passaggio da Controller1 a Controller2 e viceversa.

    Il stream del programma:

    • La prima scena contiene un campo di TextField , un Button e Label . Quando si fa clic sul Button , viene caricata e visualizzata la seconda finestra, incluso il testo inserito nel campo di TextField .
    • All’interno della seconda scena, c’è anche un campo di TextField , un Button e Label . L’ Label mostrerà il testo inserito nella TextField sulla prima scena.
    • Inserendo del testo nel campo di testo della seconda scena e facendo clic sul relativo Button , l’ Label della prima scena viene aggiornata per mostrare il testo inserito.

    Questa è una dimostrazione molto semplice e potrebbe sicuramente rappresentare un miglioramento, ma dovrebbe rendere il concetto molto chiaro.

    Il codice stesso viene anche commentato con alcuni dettagli su cosa sta accadendo e come.

    IL CODICE

    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/classs 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?>                  

    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?>                  

    Devi creare una class di contesto.

     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; } } 

    Devi solo impostare l’istanza del controller durante l’inizializzazione usando

     Context.getInstance().setTabRough(this); 

    e puoi usarlo dalla tua intera applicazione solo usando

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

    Ora puoi passare il parametro a qualsiasi controller dall’intera applicazione.

    Ecco un esempio per l’utilizzo di un controller iniettato da 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; } 

    Ecco una implementazione concreta del caricatore:

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

    Nota questo esempio carica la vista al centro di un BoarderPane che è la radice della scena nello stage. Questo è irrilevante per l’esempio (dettagli di implementazione del mio caso d’uso specifico) ma ho deciso di lasciarlo in quanto alcuni potrebbero trovare utile.

    Puoi decidere di utilizzare un elenco pubblico osservabile per memorizzare dati pubblici, o semplicemente creare un metodo di setter pubblico per archiviare i dati e recuperarli dal controller corrispondente