Вызов методов в Java Fx ActionEvent

Я новичок в JavaFX и хочу создать класс представления, который будет вызывать методы в контроллере, когда событие запускается нажатием кнопки. Мой код ниже:

package spacetrader.menu;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import spacetrader.Window;

public class MenuView implements Initializable {
    public Window window;
    public MenuCtrl menuCtrl;

    @FXML
    Button start;
    @FXML
    Pane background;
    @FXML
    Button exit;

    public MenuView() {};

    public MenuView(Window aWindow, MenuCtrl aMenuCtrl) {
        window = aWindow;
        menuCtrl = aMenuCtrl;
    }

    void renderMainMenu() {
        try {
            window.loadFXML(new FXMLLoader((getClass().getResource("MainMenu.fxml"))));
        } catch (IOException ex) {
            Logger.getLogger(MenuView.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        start.setOnAction((ActionEvent event) -> menuCtrl.newGame());
        exit.setOnAction((ActionEvent event) -> menuCtrl.closeApplication());
    }
}

Это компилируется, но когда я запускаю его и нажимаю кнопку, я получаю исключение нулевого указателя в лямбда-выражениях. Исключение относится к «menuCtrl». Как мне настроить мою программу, чтобы кнопка вызывала menuCtrl.newGame()?

Вот что я уже пробовал:

  • Я переместил команды setOnAction между различными методами.
  • Заменены вызовы методов menuCtrl операторами System.out.prinln, чтобы подтвердить, что это работает. Оно делает.
  • Я заменил методы menuCtrl на System.out.println(this), чтобы избежать проблем с областью действия. Он печатает «spacetrader.menu.MenuView@74f51979», как и ожидалось.
  • Преобразование лямбда-выражений в анонимные внутренние классы.
  • Использовал блок в качестве тела лямбды

person jaxoncreed    schedule 03.10.2014    source источник
comment
Является ли этот класс классом контроллера для того же FXML, который он загружает? (т. е. имеет ли MainMenu.fxml атрибут fx:controller="MenuView" в качестве атрибута в корневом элементе?) Это была бы необычная установка, по крайней мере, так, как у вас здесь.   -  person James_D    schedule 03.10.2014
comment
Да, контроллер fx: в MainMenu.fxml — это MenuView. Почему это странно?   -  person jaxoncreed    schedule 03.10.2014
comment
Потому что вам нужно создать один экземпляр контроллера, чтобы загрузить FXML, который затем создает другой экземпляр контроллера. Обычно вы загружаете FXML из другого класса (за исключением случаев, когда вы используете динамический корень (который выглядит совсем иначе).   -  person James_D    schedule 03.10.2014


Ответы (3)


Ваша установка необычна тем, что ваш класс контроллера (который вы назвали MenuView) является классом, который вызывает (косвенно, через ваш класс Window) метод загрузки FXMLLoader. FXMLLoader анализирует FXML, видит атрибут fx:controller и, следовательно, создает его экземпляр, используя конструктор по умолчанию (без аргументов). (Здесь я делаю предположение, что вы не вызываете setController или setControllerFactory для FXMLLoader в Window.loadFXML.) Таким образом, у вас есть два экземпляра контроллера: тот, который создал загрузчик (для которого метод initialize() никогда не вызывается, и @FXML -аннотированные поля никогда не вводятся), и тот, который был создан FXMLLoader (который вызывает конструктор без аргументов, поэтому поля window и menuCtrl не инициализируются для этого экземпляра).

Вы должны либо реорганизовать это, чтобы FXMLLoader был создан и передан классу Window для загрузки в другом месте (т. е. без создания экземпляра контроллера), либо вы должны явно установить контроллер в FXMLLoader. Вы можете достичь последнего в два шага:

  1. Удалите атрибут fx:controller из MainMenu.fxml
  2. Явно установите контроллер на экземпляр, уже созданный в вашем методе renderMainMenu:

--

FXMLLoader loader = new FXMLLoader((getClass().getResource("MainMenu.fxml"))) ;
loader.setController(this);
window.loadFXML(loader);
person James_D    schedule 03.10.2014
comment
Вы, сэр, удивительны. У меня было совершенно неправильное представление о том, как работает архитектура FX, и это все прояснило. - person jaxoncreed; 04.10.2014

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

Если вы создаете экземпляр menuCtrl в FXML, добавьте в поле аннотацию @FXML.

person Puce    schedule 03.10.2014
comment
А, это имеет смысл. Я попробую позже. - person jaxoncreed; 03.10.2014

Вы можете попробовать использовать Scene Builder, где доступна функция перетаскивания, и вы можете создать экран за считанные секунды. В разделе кода вам просто нужно объявить имя метода и реализовать этот метод в классе обработчика кода.

Вы можете получить его с здесь

person Ashik Ali    schedule 03.10.2014