224 votes

Passage de paramètres JavaFX FXML

Comment puis-je passer des paramètres à une fenêtre secondaire dans javafx ? Existe-t-il un moyen de communiquer avec le contrôleur correspondant ?

Par exemple : L'utilisateur choisit un client dans un TableView et une nouvelle fenêtre s'ouvre, montrant les informations du client.

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 serait la nouvelle fenêtre. Le problème est que je ne trouve pas le moyen d'indiquer au contrôleur où chercher les informations sur le client (en passant l'id comme paramètre).

Des idées ?

0 votes

Vérifiez si cela fonctionne aussi : stackoverflow.com/questions/14370183/

0 votes

@Alvaro : avez-vous obtenu votre solution ? pouvez-vous passer des paramètres ? d'un contrôleur à un autre fichier contrôleur ?

3 votes

Oui. jewelsea a donné une explication au niveau du livre. C'est pourquoi j'ai accepté sa réponse

299voto

jewelsea Points 40435

Approche recommandée

Cette réponse énumère les différents mécanismes permettant de passer des paramètres aux contrôleurs FXML.

Pour les petites applications, je recommande vivement de passer les paramètres directement de l'appelant au contrôleur - c'est simple, direct et ne nécessite pas de frameworks supplémentaires.

Pour les applications plus importantes et plus compliquées, il serait utile d'étudier la possibilité d'utiliser les services suivants Injection de dépendances o Bus événementiel dans votre application.

Passage des paramètres directement de l'appelant au contrôleur

Transmettez des données personnalisées à un contrôleur FXML en récupérant le contrôleur dans l'instance du chargeur FXML et en appelant une méthode sur le contrôleur pour l'initialiser avec les valeurs de données requises.

Quelque chose comme le code suivant :

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

  Stage stage = new Stage(StageStyle.DECORATED);
  stage.setScene(
    new Scene(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 nouveau FXMLLoader est construit comme indiqué dans l'exemple de code, à savoir new FXMLLoader(location) . L'emplacement est une URL et vous pouvez générer une telle URL à partir d'une ressource FXML en :

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

Veillez à NE PAS utiliser une fonction de chargement statique sur le FXMLLoader, ou vous ne serez pas en mesure d'obtenir votre contrôleur à partir de votre instance de chargeur.

Les instances de FXMLLoader elles-mêmes ne savent jamais rien des objets du domaine. Vous ne passez pas directement les objets de domaine spécifiques à l'application dans le constructeur de FXMLLoader, mais vous le faites :

  1. Construit un FXMLLoader basé sur le balisage fxml à un emplacement spécifié.
  2. Obtenir un contrôleur à partir de l'instance FXMLLoader.
  3. Invoquez des méthodes sur le contrôleur récupéré pour lui fournir des références aux objets du domaine.

Ce blog (d'un autre auteur) propose une approche différente, mais similaire, exemple .

Configuration d'un contrôleur sur le FXMLLoader

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

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

Pane mainPane = loader.load();

Vous pouvez construire un nouveau contrôleur dans le code, en passant tous les paramètres que vous voulez de votre appelant dans le constructeur du contrôleur. Une fois que vous avez construit un contrôleur, vous pouvez le définir sur une instance de FXMLLoader. antes de vous invoquez le load() instance méthode.

Pour définir un contrôleur sur un chargeur (dans JavaFX 2.x), vous NE POUVEZ PAS définir également un contrôleur de type fx:controller dans votre fichier fxml.

En raison de la limitation de la fx:controller définition dans FXML, je préfère personnellement obtenir le contrôleur à partir du FXMLLoader plutôt que de définir le contrôleur dans le FXMLLoader.

Faire en sorte que le contrôleur récupère les paramètres d'une méthode statique externe

Cette méthode est illustrée par la réponse de Sergey à la question suivante Javafx 2.0 How-to Application.getParameters() dans un fichier Controller.java .

Utiliser l'injection de dépendances

FXMLLoader prend en charge les systèmes d'injection de dépendances comme Guice, Spring ou Java EE CDI en vous permettant de définir une fabrique de contrôleurs personnalisés sur FXMLLoader. Cela fournit un callback que vous pouvez utiliser pour créer l'instance du contrôleur avec les valeurs dépendantes injectées par le système d'injection de dépendances respectif.

Un exemple d'injection de dépendance d'une application JavaFX et d'un contrôleur avec Spring est fourni dans la réponse à la question :

Une approche d'injection de dépendances vraiment agréable et propre est illustrée par la fonction cadre afterburner.fx avec un échantillon application air-hacks qui l'utilise. afterburner.fx s'appuie sur JEE6 javax.inject pour effectuer l'injection de dépendances.

Utiliser un bus d'événements

Greg Brown, créateur et implémenteur de la spécification FXML originale, suggère souvent d'envisager l'utilisation d'un bus d'événements, tel que le bus Guava EventBus pour la communication entre les contrôleurs instanciés FXML et les autres logiques d'application.

L'EventBus est une API simple mais puissante de publication/abonnement avec annotations qui permet aux POJO de communiquer entre eux partout dans une JVM sans avoir à se référer les uns aux autres.

Questions et réponses sur le suivi

Dans la première méthode, pourquoi renvoyer Stage ? La méthode peut aussi être nulle car vous avez déjà donné la commande show() ; juste avant return stage ;. Comment prévoyez-vous l'utilisation en retournant la scène

Il s'agit d'une solution fonctionnelle à un problème. Une étape est renvoyée de la showCustomerDialog de façon à ce qu'une référence à celle-ci puisse être stockée par une classe externe qui pourrait souhaiter faire quelque chose, comme masquer la scène en fonction d'un clic sur un bouton dans la fenêtre principale, à un moment ultérieur. Une autre solution, orientée objet, pourrait encapsuler la fonctionnalité et la référence à la scène dans un objet CustomerDialog ou faire en sorte qu'un CustomerDialog étende Stage. Un exemple complet d'interface orientée objet pour une boîte de dialogue personnalisée encapsulant des données FXML, de contrôleur et de modèle dépasse le cadre de cette réponse, mais pourrait faire l'objet d'un article de blog intéressant pour toute personne désireuse d'en créer une.


Informations supplémentaires fournies par l'utilisateur de StackOverflow nommé @dzim

Exemple d'injection de dépendances pour Spring Boot

La question de savoir comment faire "The Spring Boot Way", il y avait une discussion sur JavaFX 2, à laquelle j'ai répondu dans le permalien ci-joint. L'approche est toujours valable et testée en mars 2016, sur Spring Boot v1.3.3.RELEASE : https://stackoverflow.com/a/36310391/1281217


Parfois, vous voudrez peut-être renvoyer les résultats à l'appelant, auquel cas vous pourrez consulter la réponse à la question connexe :

0 votes

Les constructeurs de FXMLLoader ne prennent que les URL comme paramètres. Quelle est la manière correcte d'instancier le FXMLLoader ?

0 votes

Mise à jour de la réponse pour discuter de l'instanciation de FXMLLoader.

1 votes

Le site Web de l'EventBus auquel il est fait allusion indique : "Mise à jour 3/2013 : L'EventBus est devenu obsolète..."

18voto

Zephyr Points 3409

Je réalise que c'est un très vieux post et qu'il y a déjà d'excellentes réponses, mais je voulais faire un MCVE simple pour démontrer une telle approche et permettre aux nouveaux codeurs de voir rapidement le concept en action.

Dans cet exemple, nous utiliserons 5 fichiers :

  1. Main.java - Simplement utilisé pour démarrer l'application et appeler le premier contrôleur.
  2. Controller1.java - Le contrôleur pour la première mise en page FXML.
  3. Controller2.java - Le contrôleur pour la deuxième mise en page FXML.
  4. Layout1.fxml - La mise en page FXML de la première scène.
  5. Layout2.fxml - La mise en page FXML pour la deuxième scène.

Tous les fichiers sont listés dans leur intégralité au bas de cet article.

Le but : Pour démontrer le passage des valeurs de Controller1 a Controller2 et vice versa.

Le déroulement du programme :

  • La première scène contient un TextField , a Button et un Label . Lorsque le Button est cliqué, la deuxième fenêtre est chargée et affichée, y compris le texte saisi dans le champ TextField .
  • Dans la deuxième scène, il y a aussi une TextField , a Button et un Label . Le site Label affichera le texte saisi dans le champ TextField sur la première scène.
  • Lors de la saisie du texte dans la deuxième scène TextField et en cliquant sur son Button la première scène Label est mis à jour pour afficher le texte saisi.

Cette démonstration est très simple et pourrait certainement être améliorée, mais elle devrait rendre le concept très clair.

Le code lui-même est également commenté avec quelques détails sur ce qui se passe et comment.

LE CODE

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>

1 votes

Est-il possible de définir le contrôleur dans le fichier FXML ? Parce qu'il faut supprimer la ligne : loader.setController(this) et l'ajout du contrôleur dans le fichier FXML fait planter l'application

1 votes

Pas si le FXML est chargé depuis le contrôleur lui-même. Si vous chargez le FXML depuis la classe Main, par exemple, vous pouvez définir le contrôleur dans le fichier FXML et obtenir une référence à celui-ci à l'aide de l'option loader.getController()

0 votes

Finalement, j'ai réussi à trouver une solution, excellent exemple. Je l'ai implémenté dans mon projet et maintenant j'essaie d'ouvrir les deux fenêtres simultanément et de rendre la première modale. Malheureusement, une seule s'ouvre. Quelqu'un peut-il m'aider ?

11voto

user1503636 Points 83

Voici un exemple pour passer des paramètres à un document fxml à travers un espace de nom.

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

Définir la valeur External Text pour la variable d'espace de nom 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();
    }
}

0 votes

Il est à noter que certaines clés sont utilisées en interne : par exemple FXMLLoader.CONTROLLER_KEYWORD , FXMLLoader.LOCATION_KEY , FXMLLoader.RESOURCES_KEY et toute chaîne utilisée comme valeur pour l'option fx:id attribut.

0 votes

Merci pour cela, mon autre scène est juste un conteneur qui affiche le texte précédemment affiché sur ma scène principale. Maintenant je peux avoir un seul fxml que je peux réutiliser dans plusieurs endroits en initialisant le contenu via des variables Namepace. Je n'ai pas eu à créer de nouvelles méthodes ou à modifier mon constructeur ou mes initialisateurs - j'ai juste ajouté une variable dans mon FXML et ajouté une ligne dans mon code fxmloader dans le contrôleur principal.

7voto

Alexander Kirov Points 2112

La classe javafx.scene.Node possède une paire de méthodes setUserData(Object) et Object getUserData()

Que vous pourriez utiliser pour ajouter vos informations au Node.

Ainsi, vous pouvez appeler page.setUserData(info) ;

Et le contrôleur peut vérifier si l'information est définie. En outre, vous pouvez utiliser ObjectProperty pour le transfert de données en amont, si nécessaire.

Observez une documentation ici : http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html Avant la phrase "Dans la première version, la fonction handleButtonAction() est balisée avec @FXML pour permettre au balisage défini dans le document du contrôleur de l'invoquer. Dans le deuxième exemple, le champ du bouton est annoté pour permettre au chargeur de définir sa valeur. La méthode initialize() est annotée de manière similaire."

Ainsi, vous devez associer un contrôleur à un nœud, et définir une donnée utilisateur pour le nœud.

0 votes

Stage.getScene() -> Scene.getRoot() -> recherche récursive avec Parent.getChildrenUnmodifiable(). Cette méthode est très sale. Si quelqu'un peut suggérer quelque chose de mieux, ce serait génial.

0 votes

Il semble que Stage.getScene().getRoot() soit la bonne méthode ! Merci

4voto

diego matos - keke Points 1758

Cela fonctionne

Rappelez-vous que la première fois que vous imprimez la valeur de passage, vous obtiendrez null, Vous pouvez l'utiliser après le chargement de Windows, de même pour tout ce que vous voulez coder pour tout autre composant.

Premier contrôleur

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

Un autre contrôleur

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

1 votes

Cela fonctionne lorsque vous passez les paramètres du premier contrôleur au second, mais comment passer les paramètres du second au premier contrôleur, je veux dire après que le premier.fxml ait été chargé.

0 votes

@XlintXms voir la question connexe JavaFX FXML Passage de paramètres du contrôleur A à B et retour qui répond à votre question supplémentaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X