Хобрук: Ваш путь к мастерству в программировании

Как передать параметры конструктору контроллера при использовании `‹fx:include›`?

Я изучаю JavaFX и столкнулся с проблемой, связанной с созданием экземпляров контроллеров, которую я не могу решить. По сути, мне интересно, возможно ли сделать одно из следующего:

  1. Передать параметр конструктору контроллера при включении FXML с <fx:include>; или же
  2. Укажите пользовательский экземпляр контроллера для использования при включении файла FXML с <fx:include>.

Обратите внимание, что эти вопросы связаны. На самом деле, причина, по которой я спрашиваю о варианте (2), заключается в том, что он решит вариант (1).


Моя установка


У меня есть следующий "основной" файл FXML:

<!-- XML declaration, imports, etc. removed for brevity -->
<BorderPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml">
    <!-- ... -->
    <center>
        <!-- Note that PageSwitcher is a custom control that is capable of switching between pages — you should be able to ignore it here. -->
        <PageSwitcher fx:id="mainPageSwitcher" currentPageIndex="0">
            <!-- ... -->
            <fx:include source="dashboard.fxml" fx:id="dashboard" />
        </PageSwitcher>
    </center>
</BorderPane>

У него есть связанный контроллер, MainPaneController. Здесь показывать не буду, но могу, если надо.

Вы могли заметить, что мой основной файл FXML не имеет атрибута fx:controller вместо BorderPane, несмотря на то, что я сказал, что у него есть связанный контроллер. Это связано с тем, что вместо того, чтобы позволить FXMLLoader создать для меня контроллер (и, таким образом, лишив меня возможности передавать параметры конструктору класса контроллера), я выбрал при загрузке главной страницы FXML в моем основном классе приложения (т. е. класс, расширяющий Application), чтобы создать собственный экземпляр класса MainPaneController. Вы можете увидеть метод start() моего основного класса приложения:

@Override
public void start(Stage primaryStage) throws IOException {
    FXMLLoader mainPaneLoader;
    MainPaneController mainPaneController;
    Parent mainPane;

    // Initialize the project manager.
    projectManager = new ProjectManager(primaryStage);

    // Initialize the main pane loader.
    mainPaneLoader = new FXMLLoader();

    // Initialize the main pane controller.
    mainPaneController = new MainPaneController(projectManager);

    // Load the main pane.
    mainPaneLoader.setController(mainPaneController);
    mainPaneLoader.setLocation(getClass().getResource(MAIN_PANE_FXML_PATH));
    mainPane = mainPaneLoader.load();

    Scene mainScene;

    // Create the main scene and add it to the primary scene.
    mainScene = new Scene(mainPane);
    primaryStage.setScene(mainScene);

    // Initialize the primary stage.
    primaryStage.setTitle(APPLICATION_TITLE);

    // Show the primary stage.
    primaryStage.show();
}

Обратите внимание, что объект «менеджер проекта», определенный выше и переданный в конструктор контроллера главной панели, на самом деле является основной причиной всего этого вопроса; это (в дополнение к передаче основному контроллеру) объект, который мне нужно передать контроллеру файла FXML, который я включил в основной файл FXML, используя <fx:include>.

Теперь этот подход создания моего собственного экземпляра контроллера и передачи его FXMLLoader работает очень хорошо для меня. Это позволяет мне легко, без каких-либо запутанных размышлений, передавать параметры конструктору контроллера. Однако это работает только тогда, когда у меня есть объект FXMLoader, которому я передаю экземпляр контроллера.

В другом случае, когда я включаю FXML-файл из основного FXML-файла с помощью <fx:include>, JavaFX создает для меня контроллер, не предоставляя мне возможности (1) передать параметры конструктору контроллера, или (2) использовать мой собственный экземпляр контроллера.


Что я пробовал


Изучая эту проблему, я наткнулся на этот StackOverflow вопрос, который, казалось, имел хоть какое-то отношение к проблеме. Из него я узнал о FXMLLoader.setControllerFactory(), который поначалу казался способным решить эту проблему. Однако, чтобы использовать его, я был вынужден использовать некоторое довольно запутанное отражение, чтобы проверить, может ли конструктор типа принять мой объект, а затем использовать отражение more для создания контроллера, все время надеясь, что никакие ошибки не будут выдаваться из-за лазейки в моем коде. Я был вынужден признать, что это не сработает.

Я также экспериментировал, вместо того чтобы передавать свой объект конструктору контроллера, устанавливая объект в контроллере после инициализации контроллера. Однако это не сработало, потому что мне нужно было использовать объект в методе initialize() моего контроллера, который вызывается до того, как я устанавливаю объект в контроллере. Потенциально это можно обойти, добавив еще один метод инициализации, в котором можно найти любую функциональность, требующую объекта, возможно, с именем objectInitialized(); но тогда мне пришлось бы добавить этот метод к каждому отдельному контроллеру, которому нужна эта функциональность, и я должен был бы не забыть вызвать все эти методы в какой-то момент. Кроме того, я хотел, чтобы объект был полем final в классе контроллера; очевидно, он не может быть окончательным, если его нужно задавать извне.

Наконец, я также рассмотрел вариант, что для каждого файла FXML, который мне нужно включить в основной файл FXML, вместо того, чтобы включать его в FMXL, я мог бы сделать это из контроллера Java. Таким образом, я мог бы создать свой собственный FXMLLoader, установить на нем свой собственный экземпляр контроллера и, таким образом, решить проблему. Однако я бы, если возможно, предпочел бы сохранить весь код пользовательского интерфейса в файлах FXML.


Сводка


Таким образом, мне нужен способ передать параметры конструктору моего контроллера при использовании <fx:include>.

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

Спасибо всем за помощь!
— Джейкоб


  • Единственный способ, которым я могу думать, что это сработает в этом случае, - это использовать controllerFactory. Afaik фабрика контроллеров также используется для FXMLLoaders загрузки вложенных файлов fxml. 22.03.2020
  • Создание контроллеров по сути основано на отражении, независимо от того, как это происходит. Я думаю, что мой ответ на вопрос, который вы связали, - единственный способ пойти сюда. 22.03.2020
  • @Fabian, @James_D: Ладно, понятно. Как вы думаете, было бы хорошим подходом вместо этого включить FXML из моего контроллера Java (вариант (3) выше, в разделе «Что я пробовал»)? Сначала я как бы хотел этого избежать, но, похоже, это может быть лучшим вариантом. Я мог бы даже создать вспомогательный метод для этого, который я мог бы использовать всякий раз, когда мне нужна эта функциональность. Это или использование setControllerFactory() было бы лучше? 22.03.2020
  • @JacobLockard Это сработает; Думаю, я бы все же предпочел заводской вариант контроллера; но ваш пробег может отличаться. Еще одна вещь, которую вы можете рассмотреть, — это фабрика внедрения зависимостей, такая как Spring или Guice; это в основном обеспечит инициализацию полей в ваших контроллерах по мере необходимости, но, конечно, у них есть своя кривая обучения. 22.03.2020
  • @James_D, я решил включить его с контроллера (см. ответ ниже). Спасибо за вашу помощь; ваши комментарии действительно помогли мне прояснить ситуацию и дали мне новые идеи для любых будущих проектов. 23.03.2020

Ответы:


1

После дальнейшего рассмотрения я решил просто реализовать вариант (3) в своем вопросе в разделе «Что я пробовал». По сути, вместо использования <fx:include> я решил, что будет проще, гибче и перспективнее включить любые контроллеры, которым требуются параметры напрямую/вручную из моего кода Java.


Почему?


Когда я больше думал о своей проблеме, я понял, что у меня нет абсолютно никакой возможности передать параметр конструктору моего контроллера «из моего FXML». В конце концов, поскольку объект, который я пытался передать, был определен в моем Java-коде (основной класс приложения), не было способа использовать его в FXML. Таким образом, вся моя точка зрения на проблему изменилась.

Вариант 1

После того, как я переоценил свои варианты, я все-таки решил просто использовать FXMLLoader.setControllerFactor(); этот подход был подтвержден James_D в комментариях, которые он разместил на мой вопрос: поскольку создание экземпляров контроллера all происходит посредством отражения, использование отражения для передачи моего объекта контроллеру не обязательно будет такой плохой идеей, как Я думал. На самом деле, это, вероятно, будет работать очень хорошо.

Однако, хотя это сработало бы хорошо, если бы все, что мне нужно было сделать, это передать контроллеру один объект, что, если бы для некоторых моих контроллеров я хотел передать несколько объекты (и, возможно, разных типов)? Тогда фабрика контроллеров стала бы довольно громоздкой, так как мне пришлось бы проверять множество параметров потенциально разных типов, а затем передавать правильные.

Кроме того, что если я хочу передать объект контроллеру не из класса приложения, где определена фабрика контроллеров, а откуда-то еще? Например, представьте, что у меня есть класс приложения MyApplication.java, «основной» контроллер MainController.java и контроллер «под» основным NavigationBarController.java? Возможно, в какой-то момент я захочу передать объект не из MyApplication в NavigationBarController, а из MainController в NavigationBarController. Если бы это было так (что вполне возможно), то моя фабрика контроллеров уже бы не работала, потому что не имела бы доступа к нужному объекту. Таким образом, я сделал вывод, что setControllerFactory() мне не подойдет, по крайней мере, не очень хорошо.

Вариант 2 (что использовал я)

Другой вариант (и тот, который я в конечном итоге использовал) заключался в том, чтобы включить мои контроллеры из моего кода Java, используя FXMLLoader напрямую; и вместо того, чтобы включать все контроллеры из основного класса приложения, я мог бы включить каждый из них в контроллер, в котором он «находился».

Например, используя пример, представленный в варианте (1) выше, вместо использования фабрики контроллеров и атрибута fx:controller в файле FXML я бы удалил атрибут fx:controller из любой пары файл FXML/контроллер, которой требуется доступ к моему объекту.

Вместо этого в MyApplication я бы использовал FXMLLoader напрямую для загрузки, инициализации и добавления MainController. Затем в MainController я бы использовал FXMLLoader, чтобы включить NavigationBarController.

Теперь самая большая потенциальная слабость этого подхода заключается в том, что мне нужно будет в основном повторять код загрузки каждый раз, когда я хочу включить пару файл/контроллер FXML. Чтобы решить эту проблему, я создал своего рода «служебный класс» под названием FXMLQuickLoader, который содержит методы для простой и быстрой загрузки пар файл/контроллер FXML, но с пользовательскими объектами контроллера. Если вам интересно посмотреть код, я создал GitHub Gist, содержащий FXMLQuickLoader.java.

Вариант 3 (непроверенный)

Теперь James_D указал, что я потенциально мог бы использовать что-то, называемое «фабрикой внедрения зависимостей». Если я правильно его понял, он автоматически инициализирует поля моих контроллеров, а не мне нужно передавать объекты в конструкторы. Это решение кажется идеальным; однако, поскольку это мой первый проект JavaFX, и поскольку у меня есть некоторые ограничения по времени, я решил, что изучение этого, вероятно, займет слишком много времени.

Однако для моего следующего проекта JavaFX я, вероятно, изучу его и (настоятельно) рассмотрю возможность его использования. Я постараюсь обновить этот ответ позже, если я это сделаю.


Сводка


Подводя итог, для себя я решил, что лучше всего включить мои контроллеры вручную с помощью FXMLLoader, а не пытаться использовать одну из альтернатив. Однако я только изучаю JavaFX и не совсем разбираюсь в этом вопросе, поэтому вы должны принять этот ответ с долей скептицизма. Тем не менее, это может помочь другим пользователям, которые присоединятся к нам, и, по крайней мере, наметить некоторые потенциальные решения. Я также по-прежнему открыт для дальнейших предложений по этому вопросу.

23.03.2020

2

Этого можно добиться, внедрив синглтон (или Bean в Spring), тогда каждый контроллер, который хочет получить доступ к общим данным, просто должен получить доступ к синглтону с помощью метода getInstance, а затем восстановить его.

Это более гибко, если реализовать с помощью свойства Map‹String, Object› (вместо User) и поместить данные с помощью метода put(key, objectValue), а затем с помощью get(key);

На основании этого сообщения

//Bean or Singleton
public final class UserHolder {

private User user;
private final static UserHolder INSTANCE = new UserHolder();

private UserHolder() {}

public static getInstance() {
  return INSTANCE;
}

public void setUser(User u) {
  this.user = u;
}

public User getUser() {
  return this.user;
}
}

//put data in Controller 1
@FXML
private void sendData(MouseEvent event) {
// Step 1
User u = new User();
Node node = (Node) event.getSource();
Stage stage = (Stage) node.getScene().getWindow();
stage.close();
try {
  Parent root =     
  FXMLLoader.load(getClass().getClassLoader().getResource("fxml/SceneB.fxml"));
  // Step 2
  UserHolder holder = UserHolder.getInstance();
  // Step 3
  holder.setUser(u);
  // Step 4
  Scene scene = new Scene(root);
  stage.setScene(scene);
  stage.show();
} catch (IOException e) {
  System.err.println(String.format("Error: %s", e.getMessage()));
}
}

//recover data in Controller 2
@FXML
private void receiveData(MouseEvent event) {
  UserHolder holder = UserHolder.getInstance();
  User u = holder.getUser();
  String name = u.getName();
  String email = u.getEmail();
}
05.02.2021
Новые материалы

Получение стоковых обновлений с помощью Python
Для начинающего финансового аналитика Введение Описание: Этот проект Python создает скрипт для получения текущих обновлений акций с финансового веб-сайта Yahoo. Для этого проекта мы..

Это все, что вам нужно знать о Kotlin в 2022 году
Добро пожаловать! Kotlin — это язык программирования, популярность которого, кажется, растет, его действительно можно использовать для создания чего угодно, и если вы хотите узнать о Kotlin,..

Текстовый графический интерфейс с Lanterna на Java
Мой опыт работы с компьютерами (и текстовыми графическими пользовательскими интерфейсами) начался еще в восьмидесятых, когда я был ребенком, на дне рождения друга. Это был «новенький» Amstrad..

Перезарядите свой мозг: умопомрачительный потенциал мозговых компьютерных интерфейсов
Способность читать свои мысли и управлять объектами разумом долгое время были предметом человеческого любопытства, ограниченного областью научной фантастики… то есть до сих пор? С технологией,..

Основы C# — Нулевой оператор объединения (??)
Оператор ?? называется null-coalescing operator . Этот оператор используется для предоставления значения по умолчанию, если значение операнда в левой части оператора равно null ...

Сравнение номеров версий в C++ с использованием синтаксического анализа строк
Номера версий обычно используются для обозначения развития или обновлений программного обеспечения или любого другого продукта. При работе с номерами версий в C++ может быть полезно сравнить две..

В мир искусственного интеллекта…
ИИ — это новое топливо в современном мире. Куда бы вы ни обратились, с кем бы вы ни разговаривали — они, как правило, упоминают об ИИ хотя бы раз в ходе разговора. ИИ гудит повсюду. У каждого..