JavaFX: нужна помощь в понимании setControllerFactory - PullRequest
0 голосов
/ 13 мая 2019

Я настроил несколько пользовательских контроллеров при создании приложения, и мне понадобится некоторая помощь в организации этих контроллеров с помощью setControllerFactory в JavaFX.

Я довольно неопытен в JavaFX, но потратил довольно много времени на созданиеНебольшое приложение со Scenebuilder и JavaFX.

Фон приложения Приложение состоит из: - карты (реализованной в виде imageView) - боковой панели с кнопками и значками для событий перетаскивания.- карта также имеет отдельные слои в качестве цели для перетаскивания различных типов значков.

В качестве прототипа моего события перетаскивания я использовал инструкции Джоэля Граффа (https://monograff76.wordpress.com/2015/02/17/developing-a-drag-and-drop-ui-in-javafx-part-i-skeleton-application/). Heпишет "для того, чтобы объект был виден за пределами контейнера, он должен быть дочерним по отношению к родительскому или другому наследственному контейнеру - он должен принадлежать более высокому уровню иерархии. В случае нашего значка перетаскивания этоозначает, что мы должны были добавить его как ребенка в AnchorPane верхнего уровня RootLayout. "и он использует динамические корни для своего проекта.

Чтобы научить себя, как использовать пользовательский элемент управления с FXML, я использовал учебник Ирины Федорцовой https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm. И чтобы узнать, как настроить несколько экранов, я использовал видео https://www.youtube.com/watch?v=5GsdaZWDcdY и связанный код из https://github.com/acaicedo/JFX-MultiScreen.

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

Когда я былВ поисках дальнейших решений я неоднократно сталкивался с методом setControllerFactory.К сожалению, я не могу найти хорошее объяснение того, как правильно его использовать и какова его конкретная цель.Единственное учебное пособие, которое я нашел, было: https://riptutorial.com/javafx/example/8805/passing-parameters-to-fxml---using-a-controllerfactory, к сожалению, оно кажется немного недостаточным для моих целей.

Мне кажется, что я бы больше всего выиграл от метода / класса, с которым я мог бы организоватьвсе мои пользовательские контроллеры, загрузите и инициализируйте их в соответствующее время, а затем снова получите доступ к ним (аналогично интерфейсу и суперклассу в видео для JFX-MultiScreen).

1 Ответ

2 голосов
/ 17 мая 2019

Я неоднократно сталкивался с методом setControllerFactory.К сожалению, я не могу найти хорошее объяснение того, как правильно его использовать и какова его конкретная цель:

По умолчанию метод FXMLLoader.load() создает экземпляр контроллера, названный в документе fxml, используя 0-арг конструктор.Метод FXMLLoader.setControllerFactory​ используется, когда вы хотите, чтобы ваш объект FXMLLoader определил экземпляры контроллеров определенным образом, например, использовал другой конструктор контроллера для определенных аргументов, вызывал метод на контроллере до его возврата и т. Д., Как в

FXMLLoader loader = new FXMLLoader(...);
loader.setControllerFactory(c -> {
   return new MyController("foo", "bar");
});

Теперь при вызове loader.load() контроллер будет создан, как указано выше.Однако вызов метода FXMLLoader.setController​ на существующем контроллере может быть проще.

Мне кажется, что я бы больше всего выиграл от метода / класса, с помощью которого я мог бы организовать все свои пользовательские контроллеры, загрузитьи инициализировать их в подходящее время, а затем снова получить к ним доступ

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

Синглтон позволяет контроллерам взаимодействовать с вашим основным классом приложения, вызывая, например, MyApp.getSingleton ().Находясь в основном классе приложения, вы можете затем организовать все свои представления в частном HashMap и добавить общедоступные методы add (...), remove (...) и activ (...), которые могут добавлять или удалять представления.с карты или активируйте представление на карте (т. е. установите корень сцены для нового представления).

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

public enum View {
    LOGIN("login/Login.fxml"),
    NEW_USER("register/NewUser.fxml"),
    USER_HOME("user/UserHome.fxml"),
    ADMIN_HOME("admin/AdminHome.fxml");

    public final String location;

    View(String location) {
        this.location = "/views/" + location;
    }
}

Ниже приведен пример основного класса приложения:

public final class MyApp extends Application {

    // Singleton
    private static MyApp singleton;
    public MyApp() { singleton = this; }
    public static MyApp getSingleton() { return singleton; }

    // Main window
    private Stage stage;

    private Map<View, Parent> parents = new HashMap<>();

    @Override
    public void start(Stage primaryStage) {
        stage = primaryStage;
        stage.setTitle("My App");
        add(View.LOGIN);
        stage.setScene(new Scene(parents.get(View.LOGIN)));
        stage.show();
    }

    public void add(View view) {
        var loader = new FXMLLoader(getClass().getResource(view.location));

        try {
            Parent root = loader.load();
            parents.put(view, root);
        } catch (IOException e) { /* Do something */ }
    }

    public void remove(View view) {
        parents.remove(view);
    }

    public void activate(View view) {
        stage.getScene().setRoot(parents.get(view));
    }

    public void removeAllAndActivate(View view) {
        parents.clear();
        add(view);
        activate(view);
    }
}

Если у вас есть ресурсы всего приложения, вы можете поместить их в класс приложения и добавить методы получения / установки так,ваши контроллеры могут получить к ним доступ.Вот пример класса контроллера:

public final class Login implements Initializable {

    MyApp app = MyApp.getSingleton();

    // Some @FXML variables here..

    @FXML private void login() {
        // Authenticate..
        app.removeAllAndActivate(View.USER_HOME);
    }

    @FXML private void createAccount() {
        app.add(View.NEW_USER);
        app.activate(View.NEW_USER);
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {}
}
...