JavaFX + Spring: возможно ли избежать жесткого связывания главного приложения и контроллера?

135
20 ноября 2020, 05:00

В данном учебном примере предлагается создать тестовые данные прямо в главном классе приложения, а также вызывать из него метод JavaFX-контроллера setMainApp(MainApp mainApp), который даст доступ к данным personData. Другими словами, контроллер и главный класс приложения жёстко связаны.

Приведённый ниже код пока что НЕ мой:

MainApp.java

public class MainApp extends Application {
  private Stage primaryStage;
  private BorderPane rootLayout;
  private ObservableList<Person> personData = FXCollections.observableArrayList();
  public MainApp() {
    personData.add(new Person("Hans", "Muster"));
    personData.add(new Person("Ruth", "Mueller"));
    // ... и другие
  }

  @Override
  public void start(Stage primaryStage) {
    this.primaryStage = primaryStage;
    this.primaryStage.setTitle("AddressApp");
    initRootLayout();
    showPersonOverview();
  }
  public static void main(String[] args) {
    launch(args);
  }
  // ...
  public void showPersonOverview() {
    try {
      // Загружаем сведения об адресатах.
      FXMLLoader loader = new FXMLLoader();
      loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
      AnchorPane personOverview = (AnchorPane) loader.load();
      // Помещаем сведения об адресатах в центр корневого макета.
      rootLayout.setCenter(personOverview);
      // Даём контроллеру доступ к главному приложению.
      PersonOverviewController controller = loader.getController();
      controller.setMainApp(this);
    } catch (IOException e) {
        e.printStackTrace();
    }
  }
  /** Возвращает данные в виде наблюдаемого списка адресатов. */
  public ObservableList<Person> getPersonData() {
      return personData;
  }
}

PersonOverviewController.java

public class PersonOverviewController {
  // ...
  // Ссылка на главное приложение.
  private MainApp mainApp;
  public PersonOverviewController() {}
  /**
   * Инициализация класса-контроллера. Этот метод вызывается автоматически
   * после того, как fxml-файл будет загружен.
   */
  @FXML
  private void initialize() {
      // Инициализация таблицы адресатов с двумя столбцами.
      firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
      lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
  }
  /** Вызывается главным приложением, которое даёт на себя ссылку. */
  public void setMainApp(MainApp mainApp) {
      this.mainApp = mainApp;
      // Добавление в таблицу данных из наблюдаемого списка
      personTable.setItems(mainApp.getPersonData());
  }
}

Модифицируя данное учебное приложение, я решил получать данные не из главного класса, из Spring-бина PeopleRepository:

public class PeopleRepository implements IPeopleRepository {
  public ObservableList<Person> getPeople() {
    ObservableList<Person> people = FXCollections.observableArrayList();
    people.add(new Person( "Muster", "Hans"));
    people.add(new Person("Mueller", "Ruth"));
    // ... и другие
    return people;
  }
}

Я не знаю, зачем ещё кроме данных нам нужен доступ к главному классу приложения, но теперь, когда мы можем получить данные из бина, setMainApp нам вроде как больше не нужен. Исходя из того, что мы загружаем FXML с помощью FXMLLoader в главном классе приложения, связь между главным классом приложения и View есть, а во View есть ссылка на соответствующие контроллеры. На основе этой логики, я упростил PersonOverviewController следующим образом:

public class PersonOverviewController {
  @FXML
  private TableView<Person> peopleTable;
  @FXML
  private TableColumn<Person, String> familyNameColumn;
  @FXML
  private TableColumn<Person, String> givenNameColumn;
  @FXML
  private Label familyNameLabel;
  @FXML
  // ... и другие компоненты

  public PersonOverviewController() {}
  @FXML
  private void initialize() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    IPeopleRepository peopleRepository = (PeopleRepository) applicationContext.getBean("PeopleRepository");
    familyNameColumn.setCellValueFactory(cellData -> cellData.getValue().familyNameProperty());
    givenNameColumn.setCellValueFactory(cellData -> cellData.getValue().givenNameProperty());
    peopleTable.setItems(peopleRepository.getPeople());
  }
}

Хотя приложение собирается Maven-ом без ошибок, метод initialize() просто-напросто не вызывается (System.out.println("test"); в данном метода не выполняется). Естественно, в таблице никаких данных нет. Что я сделал неправильно?

Полный листинг главного класса приложения:

public class MainApp extends javafx.application.Application {
  private Stage primaryStage;
  private BorderPane rootLayout;
  public static void main (String[] args) {
    launch();
  }
  @Override
  public void start(Stage primaryStage) {
    this.primaryStage = primaryStage;
    this.primaryStage.setTitle("JavaFX тест");
    initializeRootLayout();
    showPersonOverview();
  }

  private void initializeRootLayout() {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/RootLayout.fxml"));
    try {
      rootLayout = loader.load();
      Scene scene = new Scene(rootLayout);
      primaryStage.setScene(scene);
      primaryStage.show();
    } catch (IOException exception) {
      exception.printStackTrace();
    }
  }
  private void showPersonOverview() {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/PersonOverview.fxml"));
    try {
      AnchorPane personOverview = loader.load();
      rootLayout.setCenter(personOverview);
    } catch (IOException exception) {
      exception.printStackTrace();
    }
  }
}
Answer 1

Не понял до конца, в чём дело, но всё разрешилось, когда я изменил способ поиска шаблонов.

В туториале изначально был предложен такой способ поиска шаблонов:

loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));

В JavaFX 11 это не работает. Кроме того,

Создайте новый класс внутри пакета view и назовите его PersonOverviewController.java. (Мы должны разместить этот класс-контроллер в том же пакете, где находится файл разметки PersonOverview.fxml, иначе Scene Builder не сможет его найти.

тоже нынче не актуально. Рабочим же является это решение (ссылка на англоязычный оригинал):

FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/main.fxml"));
Parent content = loader.load(); 

Шаблон main.fxml будет найден, если его положить в папку src/main/resources. После того, как я это сделал, основная проблема данного вопроса была также решена.

READ ALSO
Как сделать генерацию чисел от 0 до 1

Как сделать генерацию чисел от 0 до 1

Как сделать генерацию чисел от 0 до 1Если выпадет 0 то человек победил и в консоле выводит это

132
Получение текста с владки JTabbedPane

Получение текста с владки JTabbedPane

использую JTabbedPane в "текстовом редакторе" и хне знаю как получить содержимое вкладки JTabbedPaneПробовал делать через getTabComponentAt() он возвращает...

190
Как лучше поступить с условиями?

Как лучше поступить с условиями?

У меня есть переменная d которую пользователь задает сам, она принимает значения от 1 до 99, каждое число должно исполнять свою роль, например...

164
.length переменной обнуляется

.length переменной обнуляется

Пишу окно ввода для калькулятора - ограничиваю 15 по длине

133