Передача параметра во вложенный контроллер - PullRequest
0 голосов
/ 07 марта 2019

Я хотел бы спросить о структуре программы.

I have ControllerStatistic, связанной с FXMLStatistic, в которой я определяю TabPane.

В initialize в ControllerStatistic Я добавляюВкладка за каждый месяц.Каждая вкладка содержит FXMLTableViewMonthly с ControllerMonthlyControllerMonthly я хотел бы заполнить таблицу строками для каждого дня месяца.У меня есть информация о месяце из статического поля:

private static int countControllers = 0;

public ControllerUdostepnianieNaZewnatrz() {
    countControllers++;
    monthNumber = countControllers;
}

Я заполняю таблицу в initialize.

Это работает, но я не думаю, что это правильный путь.


Я бы хотел передать параметр месяца в ControllerStatistic в ControllerMonthly.

Здесь я вижу 2 варианта:

В ControllerStatistic Я получаю контроллер из загрузчика и устанавливаю месяц,затем в ControllerMonthly я не могу заполнить в initialize (поле месяца пусто) Так что мне нужно заполнить в ControllerStatistic после установки поля месяца.

Я также могу удалить fx:controller из FXML и построитьновый контроллер в коде, как описано в Передача параметров JavaFX FXML от @jewelsea (он отметил, что ему не нравится это решение).Тогда я думаю, что я могу заполнить в ControllerMonthly в initialize.

Я предпочитаю использовать 2-й подход.Сначала выглядит очень плохо для меня (заполнить после установки месяца - решение выглядит как вызывающее много ошибок).

Как это сделать?

Ответы [ 2 ]

1 голос
/ 07 марта 2019

Ну нет вообще плохого или хорошего.Это зависит от вашего варианта использования / дизайна и вкуса.

Давайте сначала посмотрим на другие FX-элементы без fxml и то, как вы их заполнили, чтобы встать на правильный путь.Возьмите AnchorPane например.Сначала вы создаете его, а после его создания вы заполняете его дополнительными элементами.Когда вы закончите, вы показываете все это.Вы не переписываете какой-либо метод initialize () в AnchorPane:

public void createAStage(String foo){ 
     AnchorPane pane = new AnchorPane();
     Stage stage = new Stage();
     Scene scene = new Scene(pane);
     stage.setScene(scene);
     //here we populate the pane with a Label
     //and set that Label again to some value that was passed to this method(foo):
     pane.getChildren().add(new Label(foo));
     stage.show();
}

В этом нет ничего плохого.И поэтому нет ничего плохого в установке данных в некотором классе, который был создан из fxml после вызова initialize ().И да, в этом случае вы заполняете не initialize (), а извне на своей фабрике - ну и что?

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

public class DialogController implements Initializable {     
    @FXML
    private AnchorPane dialog;
    @FXML
    private Label lb_size;
    private Setting settings = null;
    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {}

    public void setSettings(Settings settings, int size) {
        this.settings = settings;
        this.lb_size.setText("" + size);
    }
}

Затем я создаю его:

public DialogController createDialog(Settings settings, int size){
      final FXMLLoader loader = new FXMLLoader(FXMLLoader.class.getResource("/fxml/afxm.fxm"));
      try {
            final Stage stage = new Stage();
            stage.setScene(new Scene(loader.load()));
            final DialogController controller = loader.getController();
            controller.setSettings(settings,size);
            stage.show();
            return controller;
       } catch (IOException ex) {
            throw new InternalApplicationError("Resource missing", ex);
       }
  }

Теперь, когда мне нужно установить Настройки на что-то другое, я бы назвал:

controller.setSettings(settings,size);

Это, конечно, терпит неудачу, если было ограничение rg.эти настройки никогда не могут быть нулевыми.Обычно, если вы можете / хотите переназначить значения, вы должны в любом случае позаботиться об этом случае, чтобы ваш класс мог обрабатывать его при помощи settings = null, как это может произойти, если вы его повторно определили.Так что вы должны где-то проверить это и убедиться, что у вас нет нулевого указателя.То же самое верно для поля размера - если оно не было установлено до его показа, оно будет показывать значение по умолчанию - но это может быть очень хорошо, что вы хотите.

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

Для этого у меня есть простой базовый класс:

public class FXMLStage extends Stage implements Initializable {

    protected URL url = null;
    protected ResourceBundle resourceBundle = null;

    @SuppressWarnings("LeakingThisInConstructor")
    public FXMLStage(String filename) {
        final FXMLLoader loader = new FXMLLoader(FXMLLoader.class.getResource(filename));
        try {
            loader.setControllerFactory(p -> this);
            this.setScene(new Scene(loader.load()));
        } catch (IOException ex) {
            throw new InternalApplicationError("Resource missing", ex);
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        this.url = url;
        this.resourceBundle = rb;
    }
}

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

Я установил контроллер с loader.setControllerFactory (p-> this) вместо loader.setController (this) по одной единственной причине: я могу автоматически создавать / обновлять код Java для контроллера.Среда IDE может создавать / обновлять поля в контроллере автоматически из fxml, если в fxml установлен шаблон.И если в fxml установлен контроллер, вы не можете установить его явно в коде.Так что для моего удобства это скорее обходной путь.

Если бы не это, я бы предпочел установить простой контроллер с помощью loader.setController (this);

Также я не проверяю класс, который передается в p: "loader.setControllerFactory (p -> this);"- вы можете захотеть сделать это, поскольку, конечно, произойдет сбой, если fxml не соответствует контроллеру (неправильный класс).Но я скорее хочу, чтобы он потерпел неудачу, если что-то не так (неправильный fxml для контроллера), вместо этого молча продолжаетсяТаким образом, сообщение об ошибке, говорящее мне, что я использую неправильный контроллер, приемлемо для меня.Более того: он также потерпит неудачу, если у вас есть вложенные контроллеры - в этом случае вы наверняка захотите проверить класс и вернуть соответствующий контроллер - и в этом случае я скорее использую реальную фабрику.

Теперь из этого базового классаЯ получаю определенный класс контроллера:

public class SampleDialog extends FXMLStage {
     @FXML
     private AnchorPane dialog;
     @FXML
     private Label lb_size; 
     //....
     //some additional fields to initialize... 
     private final Session session; 
     public  SampleDialog(Session session, int size) {
        super("/fxml/SampleDialog.fxml");
        //initialize aditional fields:
        this.session=session;
        lb_size.setText("" + size);
    }
}

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

Обратите внимание на объект "Session", который передается конструктору (не имеет значения, какие это могут быть данные - у вас будут данные вашего типа).Он сохраняется как ссылка в диалоге.Поэтому любые изменения, происходящие с данными извне, напрямую отражаются в диалоговом окне.

Если бы это было, например, наблюдаемое, вы могли бы привязать к нему элементы своего диалога, что всегда отражало бы состояние этих данных.
Если бы это был ObservableList, вы могли бы заполнить ListView в диалоге с ним, и всякий раз, когда этот ObservableList изменяется, ListView будет отражать состояние списка. То же самое для TableView (например, я заполняю TableViews из HashMaps, которые создаются и заполняются / обновляются где-то еще). Таким образом, становится возможным разделение модели, вида и контроллера.

Помните только о специальной цели initialize (). Это часть процесса строительства! Поэтому, если вы перезаписываете его, не все поля могли быть инициализированы еще при его вызове, и поэтому он может потерпеть неудачу, если вы попытаетесь использовать одно из этих неинициализированных полей. Вот в чем заключается метод inizialize (): инициализация неинициализированных полей и их имени должны дать вам справедливое предупреждение.

Теперь я хочу использовать это:

SampleDialog dialog = new SampleDialog(session,5);
dialog.show();

Или, если мне не нужен объект:

new SampleDialog(session,5).show();

Последнее замечание: у меня не было вашего контроллера, поэтому я не могу создавать примеры, которые можно найти. Я использовал Stage, потому что его просто воспроизвести, но использование Tabs и TableViews не сильно отличается. Также я не пытался дать вам все виды подходов - они есть в вашем связанном вопросе. Я попытался привести несколько примеров того, как различные подходы и сценарии могут выглядеть в приложении реального мира и что может произойти в примерах - в надежде вызвать некоторое представление о том, что происходит, и показать, что существует компромисс между намного больше, чем два пути. Удачи!

1 голос
/ 07 марта 2019

Вы также можете переопределить ControllerFactory для вашего FXMLLoader.Что-то вроде:

    loader.setControllerFactory((Class clazz) -> {
        if (clazz.isAssignableFrom(SomeClass.class)) {
            return new SomeClass(getMonthNumber());
        } else {
            return clazz.newInstance();
        }
    });
...