Несколько FXML с контроллерами, общий объект

Добрый вечер всем,

Я уже нашел кучу сообщений по этой теме, но мне все еще не удается передать объект из Controller1 в Controller2. Есть ли где-нибудь полный учебник или пример проекта, который делает это?

Я зашел так далеко, пока не застрял:

Класс страны

public class Country {
private SimpleStringProperty country = new SimpleStringProperty("");

//Constructor
public Country() {
}

//GETTERS
public String getCountry() {
    return country.get();
}

//SETTERS
public void setCountry(String value) {
    country.set(value);
}

@Override
public String toString() {
    return getCountry();
}
}

При запуске программы загружается основной FXML (Sample.fxml). Он содержит область границы со строкой меню на верхней панели и панель содержимого в центре. При инициализации я создаю новый объект Country и сохраняю его в глобальной переменной. У меня есть метод, который загружает другой FXML в панель содержимого при нажатии на элемент меню:

SampleController.java

public class SampleController implements Initializable {

@FXML
private Pane pContent;

private Country c;

@FXML
private void handleButtonAction(ActionEvent event) throws IOException {
    System.out.println(c); //this prints Belgium, which is correct

    URL url = getClass().getResource("Sub1.fxml");

    FXMLLoader fxmlloader = new FXMLLoader();
    fxmlloader.setLocation(url);
    fxmlloader.setBuilderFactory(new JavaFXBuilderFactory());

    pContent.getChildren().clear();
    pContent.getChildren().add((Node) fxmlloader.load(url.openStream()));
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    c = new Country();
    c.setCountry("Belgium");
}

public Country getCountryFromSampleController(){
    return c;
}
}

Теперь я хочу захватить объект Country при загрузке Sub1.fxml, а это значит, что мне нужно получить объект Country при инициализации():

Sub1Controller.java

public class Sub1Controller implements Initializable {

/**
 * Initializes the controller class.
 */
@Override
public void initialize(URL url, ResourceBundle rb) {
    SampleController sp = new SampleController(); //I don't know how to fetch the original SampleController object
    System.out.println(sp.getCountryFromSampleController()); 
    //this prints null, which is ofcourse logical because I make a new SampleController object.         
}    
}

Вопрос, который у меня есть, как я могу получить «исходный» объект SampleController, чтобы я мог использовать метод getCountryFromRoot() для получения объекта Country со значением Belgium? Я часами искал эту проблему и читал все сообщения на StackOverflow об этом, но, похоже, я не нашел недостающей ссылки... любая помощь (желательно с этим кодом) приветствуется!

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


person Perneel    schedule 28.08.2012    source источник


Ответы (2)


FXML — это простая форма шаблона MVC. Файл FXML - это вид, Контроллер очевиден, что пропущено? Модель — место, где вы храните все данные, относящиеся к вашему текущему представлению, и, таким образом, которые вы можете использовать для обмена данными о странах между контроллерами.


1. Одним из возможных подходов к внедрению модели является «контекст». Давайте рассмотрим случай, когда у вас есть только одна модель для всего проекта, поэтому вы можете иметь глобальный контекст в виде Singleton

public class Context {
    private final static Context instance = new Context();

    public static Context getInstance() {
        return instance;
    }

    private Country country = new Country();

    public Country currentCountry() {
        return country;
    }
}

Ваш SampleController будет иметь следующие изменения:

@Override
public void initialize(URL url, ResourceBundle rb) {
    Context.getInstance().currentCountry().setCountry("Belgium");
}

И SubController1 может получить к нему доступ таким же образом:

@Override
public void initialize(URL url, ResourceBundle rb) {
    System.out.println(Context.getInstance().currentCountry().getCountry());
}

2. Другой способ — передать контекст в SubController1, а затем загрузить его XML. Это будет работать лучше, если вы не хотите иметь глобальную модель приложения. Итак, создайте аналогичный класс Context, но без полей экземпляра, и:

public class Sub1Controller implements Initializable {
    private Context context;
    public void setContext(Context context) {
        this.context = context;
        // initialize country dependent data here rather then in initialize()
    }
}

Настройка контекста в SampleController:

Context currentContext = new Context();

@Override
public void initialize(URL url, ResourceBundle rb) {
    currentContext.currentCountry().setCountry("Belgium");
}

@FXML
private void handleButtonAction(ActionEvent event) throws IOException {
    URL url = getClass().getResource("Sub1.fxml");

    FXMLLoader fxmlloader = new FXMLLoader();
    fxmlloader.setLocation(url);
    fxmlloader.setBuilderFactory(new JavaFXBuilderFactory());

    pContent.getChildren().clear();
    pContent.getChildren().add((Node) fxmlloader.load(url.openStream()));
            // here we go
    ((Sub1Controller)fxmlloader.getController()).setContext(currentContext);
}
person Sergey Grinev    schedule 28.08.2012
comment
Миллион раз спасибо Сергей, наконец-то все стало понятно. Это первый раз, когда мне нужно создать большое приложение, но я все еще пытаюсь понять, как его «создать». У меня также есть еще один секретный вопрос, который я хотел бы задать, но я не уверен, что это правильный сайт, чтобы задать его (это не связано с кодом) - person Perneel; 29.08.2012
comment
Добрый день, Сергей, извините, что поднимаю старую тему. Это все еще работает как шарм, но что мне интересно... Есть ли способ максимизировать ширину и высоту объекта Node (Node) fxmlloader.load(url.openStream()), чтобы он был привязан к ширине и высоте панели (pContent)? - person Perneel; 10.01.2013
comment
он должен работать автоматически, если нет, лучше предоставьте свой код в отдельном вопросе - person Sergey Grinev; 10.01.2013
comment
Привет Сергей, как и просили: stackoverflow.com/questions/14265697/ - person Perneel; 10.01.2013
comment
Мне нравится идея шаблона Singleton, но будет ли считаться плохой практикой просто установить переменные в главном контроллере на static и ссылаться на них из подконтроллеров? - person blo0p3r; 05.02.2013
comment
это будет работать для небольшого проекта, но, как правило, это плохая практика, см. stackoverflow.com/questions/3151768/ - person Sergey Grinev; 05.02.2013
comment
Используя опцию #2. Если вы используете эти данные при инициализации своих данных, вы получите доступ к своему контроллеру только ПОСЛЕ того, как ваша панель загрузится. Есть ли способ обойти это? Я думаю, что это может быть вопрос сам по себе, но, поскольку вы касаетесь этого, это уже может хорошо подойти здесь. - person blo0p3r; 06.02.2013
comment
Здесь был дан ответ: stackoverflow.com/a/14190310/686036. Просто... следует подумать об этом. - person blo0p3r; 06.02.2013
comment
Или еще проще: public enum Context { INSTANCE; }. - person assylias; 12.08.2013
comment
@SergeyGrinev: не могли бы вы помочь мне с этим? stackoverflow.com/q/22243243/2722799 - person Java Man; 10.03.2014
comment
Спасибо @SergeyGrinev Это сотворило со мной чудеса. Мне нужно было получить значения строки таблицы на других контроллерах, и ваш ответ помог мне сделать это после множества поисков в Google. Спасибо. - person Joseph; 13.01.2016
comment
@SergeyGrinev мы создаем только один Context для многих моделей, или у каждой модели есть определенные Context? например, у меня есть Country и Person - мои модели. - person Menai Ala Eddine - Aladdin; 26.03.2018
comment
@MenaiAlaEddine это полностью зависит от вас. Я бы сказал, что для небольших проектов один контекст более удобен. - person Sergey Grinev; 26.03.2018

с помощью Flow API DataFX вы можете вводить данные в свои экземпляры контроллера с помощью CDI:

person Hendrik Ebbers    schedule 27.03.2014