Параметры передачи JavaFX FXML

javafx parameters dependency-injection parameter-passing fxml


Как передать параметры во вторичное окно в javafx? Есть ли способ связи с соответствующим контроллером?

Например: пользователь выбирает клиента из TableView , и открывается новое окно с информацией о клиенте.

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 будет новым окном. Проблема в том, что я не могу найти способ сообщить контроллеру, где искать информацию о клиенте (передавая id в качестве параметра).

Есть идеи?





Answer 1 jewelsea


Рекомендуемый подход

В этом ответе перечисляются различные механизмы передачи параметров FXML-контроллерам.

Для небольших приложений я настоятельно рекомендую передавать параметры непосредственно от вызывающего абонента к контроллеру-это просто,просто и не требует дополнительных каркасов.

Для более крупных и сложных приложений было бы целесообразно выяснить, хотите ли вы использовать в своем приложении механизмы Dependency Injection или Event Bus .

Передача параметров напрямую от вызывающего к контроллеру

Передача пользовательских данных на FXML-контроллер путем извлечения контроллера из экземпляра загрузчика FXML и вызова метода на контроллере для инициализации его с требуемыми значениями данных.

Что-то вроде следующего кода:

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

Новый FXMLLoader создается, как показано в примере кода, т.е. new FXMLLoader(location) . Расположение является URL-адресом, и вы можете создать такой URL-адрес из ресурса FXML:

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

Будьте осторожны, НЕ используйте функцию статической загрузки на FXMLLoader, иначе вы не сможете получить свой контроллер от экземпляра загрузчика.

Сами экземпляры FXMLLoader никогда ничего не знают об объектах домена.Вы не передаете напрямую в конструктор FXMLLoader специфические для приложений доменные объекты,а передаете их непосредственно вам:

  1. Построить FXMLLoader на основе fxml разметки в указанном месте
  2. Получите контроллер из экземпляра FXMLLoader.
  3. Вызовите методы на получаемом контроллере для предоставления контроллеру ссылок на объекты домена.

Этот блог (другого автора) предоставляет альтернативный, но похожий пример .

Настройка контроллера на FXMLLoader

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

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

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

Вы можете создать новый контроллер в коде, передавая любые параметры из вызывающей программы в конструктор контроллера. Создав контроллер, вы можете установить его на экземпляр FXMLLoader, прежде чем вызывать метод экземпляра load() .

Чтобы установить контроллер в загрузчике (в JavaFX 2.x), вы НЕ МОЖЕТЕ также определить атрибут fx:controller в вашем файле fxml.

В связи с ограничением определения fx:controller в FXML, я лично предпочитаю получать контроллер из FXMLLoader, а не устанавливать контроллер в FXMLLoader.

Наличие контроллера, получающего параметры из внешнего статического метода

Этот метод иллюстрируется ответом Сергея на Javafx 2.0 How-to Application.getParameters () в файле Controller.java .

Использовать инъекцию зависимостей

FXMLLoader поддерживает системы впрыска зависимостей,такие как Guice,Spring или Java EE CDI,позволяя установить заводской контроллер на FXMLLoader.Это обеспечивает обратный вызов,который можно использовать для создания экземпляра контроллера с зависимыми значениями,инжектируемыми соответствующей системой инжекции зависимостей.

В ответе приведен пример внедрения JavaFX-приложений и контроллерных зависимостей с помощью Spring:

Действительно хороший, чистый подход к внедрению зависимостей иллюстрируется фреймворком afterburner.fx с примером приложения air-hacks, которое его использует. afterburner.fx использует Jav6 javax.inject для выполнения внедрения зависимостей.

Используйте шину событий

Грег Браун (Greg Brown), создатель и разработчик оригинальной спецификации FXML, часто предлагает рассмотреть возможность использования шины событий, такой как Guava EventBus , для связи между экземплярами контроллера FXML и другой логикой приложения.

EventBus-это простой,но мощный API для подписки издателей с аннотациями,который позволяет POJO общаться друг с другом в любом месте JVM без необходимости ссылаться друг на друга.

Последующие вопросы и ответы

по первому методу,зачем ты возвращаешь Сцену? Метод также может быть пустым,так как вы уже даете команду show();непосредственно перед возвращением стадии;.Как вы планируете использовать метод,возвращая Stage

Это функциональное решение проблемы. Этап возвращается из функции showCustomerDialog , так что ссылка на него может быть сохранена внешним классом, который может захотеть что-то сделать, например, скрыть этап на основе нажатия кнопки в главном окне в более позднее время. Альтернативное объектно-ориентированное решение может инкапсулировать функциональность и ссылку на стадию в объекте CustomerDialog или иметь расширенную стадию CustomerDialog. Полный пример объектно-ориентированного интерфейса с настраиваемым диалоговым окном, инкапсулирующим данные FXML, контроллера и модели, выходит за рамки этого ответа, но может стать достойной записью в блоге для любого, кто хочет его создать.


Дополнительная информация предоставлена ​​пользователем StackOverflow с именем @dzim

Пример для инъекции зависимостей весенней загрузки

На вопрос, как это сделать «The Spring Boot Way», шла дискуссия о JavaFX 2, на которую я ответил в прикрепленной постоянной ссылке. Этот подход все еще действителен и протестирован в марте 2016 года на Spring Boot v1.3.3.RELEASE: https://stackoverflow.com/a/36310391/1281217


Иногда вам может понадобиться передать результаты обратно вызывающему абоненту,и в этом случае вы можете проверить ответ на соответствующий вопрос:




Answer 2 Zephyr


Я понимаю,что это очень старый пост и у него уже есть отличные ответы,но я хотел сделать простой MCVE,чтобы продемонстрировать один такой подход и дать возможность новым программистам быстро увидеть концепцию в действии.

В данном примере мы будем использовать 5 файлов:

  1. Main.java - просто используется для запуска приложения и вызова первого контроллера.
  2. Controller1.java - Контроллер для первого макета FXML.
  3. Controller2.java - Контроллер для второго макета FXML.
  4. Layout1.fxml - макет FXML для первой сцены.
  5. Layout2.fxml - макет FXML для второй сцены.

Все файлы перечислены полностью в нижней части этого поста.

Цель: продемонстрировать передачу значений от Controller1 к Controller2 и наоборот.

Ход программы:

  • Первая сцена содержит TextField , Button и Label . При нажатии на Button загружается и отображается второе окно, включая текст, введенный в TextField .
  • Во второй сцене также есть TextField , Button и Label . Label будет отображать текст, введенный в TextField на первой сцене.
  • После ввода текста в поле TextField второй сцены и нажатия его Button Label первой сцены обновляется для отображения введенного текста.

Это очень простая демонстрация и,безусловно,может означать некоторое улучшение,но она должна сделать концепцию очень четкой.

Сам код также комментируется с некоторыми подробностями о том,что и как происходит.

КОД

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


javafx.scene.Node класс имеет пару методов setUserData(Object)и Object getUserData().

Который вы можете использовать для добавления вашей информации в Узел.

Итак,вы можете вызвать page.setUserData(info);

А контроллер может проверить,установлена ли информация.Также,при необходимости,можно использовать ObjectProperty для обратной передачи данных.

Ознакомьтесь с документацией здесь: http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html Перед фразой «В первой версии handleButtonAction () помечен как @FXML чтобы позволить разметке, определенной в документе контроллера, вызывать ее. Во втором примере поле кнопки аннотировано, чтобы позволить загрузчику установить его значение. Метод initialize () также аннотирован. "

Итак,вам нужно связать контроллер с узлом,и установить пользовательские данные на узел.




Answer 4 user1503636


Приведем пример для передачи параметров в fxml документ через пространство имён.

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

Определите значение External Text для переменной пространства имен 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


Это работает ..

Помните, что в первый раз, когда вы печатаете передаваемое значение, вы получите нулевое значение. Вы можете использовать его после загрузки окон, то же самое для всего, что вы хотите закодировать для любого другого компонента.

Первый контроллер

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

Другой контроллер

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