Вызовите метод контроллера javafx fxml из другого класса, чтобы обновить представление таблицы

Я пытаюсь обновить табличное представление javafx, определенное в моем контроллере fxml, вызвав определенный метод FXMLDocumentController.onAddSystemMessage() из другого метода служебного класса приложения GlobalConfig.addSystemMessage().

Вот мой основной класс приложения, в который я загружаю fxml:

public class Main extends Application {
    ...
    public static void main(String[] args) throws IOException {
        Application.launch(args);
    }
    ...
    @Override
    public void start(Stage primaryStage) throws IOException {
        AnchorPane page = (AnchorPane) FXMLLoader.load(Main.class.getResource("FXMLDocument.fxml"));
        Scene scene = new Scene(page, initWidth, initHeight);
        primaryStage.setScene(scene);

        currentPane(scene, page);
        primaryStage.show();
    }

Итак, вот некоторые части FXMLDocumentController:

public class FXMLDocumentController implements Initializable {
    ...
    @FXML
    private TableView<SystemMessage> systemMessages;
    @FXML
    private TableColumn<SystemMessage, DateTime> dateSysReceived;
    @FXML
    private TableColumn<SystemMessage, String> messageText;
    @FXML
    private TableColumn<SystemMessage, String> messageType;
    ...
    private ObservableList<SystemMessage> messagesData;
    ...
    private GlobalConfig globalConfig;
    ...
    @Override
    @FXML
    public void initialize(URL url, ResourceBundle rb) {
        config = new GlobalConfig();
        ...
        messagesData = FXCollections.observableArrayList();
        messagesData = getAllMessages();
        systemMessages.getItems().setAll(messagesData);
        dateSysReceived.setCellValueFactory(new PropertyValueFactory<>("dateSysReceived"));
        messageText.setCellValueFactory(new PropertyValueFactory<>("messageText"));
        messageType.setCellValueFactory(new PropertyValueFactory<>("messageType"));
    ...
    }
    ...
    private ObservableList<SystemMessage> getAllMessages() {
        ObservableList<SystemMessage> data = FXCollections.observableArrayList();
        data = FXCollections.observableArrayList();

        SystemMessageDAO msgDAO = new SystemMessageDAOImpl();
        List<SystemMessage> allMessages = new ArrayList<>();
        allMessages = msgDAO.listSystemMessage();

        for(SystemMessage msg: allMessages) {
            data.add(msg);
        }

        return data;
    }
    ... // and here is the method that i would like to call to add new record to tableview

    public void onAddSystemMessage(SystemMessage systemMessage) {
        log.info("Add System Message called!");
        // to DO ... add item to tableview
        //this method should be called when inserting new systemMessage (DAO)
    }

Вот также мой служебный класс с методом добавления системного сообщения в базу данных. Кроме того, я хотел бы вызвать метод FXMLDocumentController.onAddSystemMessage(...) для обновления таблицы новым элементом:

public final class GlobalConfig {
    ...
    //constructor
    public GlobalConfig () {
        ...
    }

    public void addSystemMessage(String messageText, String messageType) {

        SystemMessage msg = new SystemMessage();
        DateTime cur = DateTime.now();

        try {
            msg.setDateSysReceived(cur);
            msg.setMessageText(messageText);
            msg.setMessageType(messageType);
            SystemMessageDAO msgDAO = new SystemMessageDAOImpl();
            msgDAO.addSystemMessage(msg);

            FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocumentController.fxml"));
            FXMLDocumentController controller = (FXMLDocumentController)loader.getController();
            //just call my Controller method and pass msg
            controller.onAddSystemMessage(msg);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • GlobalConfig — это служебный класс, который имеет несколько методов для извлечения параметров из базы данных, а также выполняет некоторые задания, такие как добавление новых значений в таблицы базы данных. Он вызывается из нескольких частей моего приложения, и я хотел бы получить текущий объект FXMLDocumentController и вызвать его метод onAddSystemMessage() для обновления пользовательского интерфейса.

Вышеприведенная реализация соответствует: Доступ к классу контроллера FXML, однако я получаю:

java.lang.NullPointerException
at com.npap.utils.GlobalConfig.addSystemMessage(GlobalConfig.java:85)
at com.npap.dicomrouter.FXMLDocumentController.startDcmrcvService(FXMLDocumentController.java:928)
at com.npap.dicomrouter.FXMLDocumentController.initialize(FXMLDocumentController.java:814)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at com.npap.dicomrouter.Main.start(Main.java:141)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$163(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$176(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$174(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$175(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)

Надеюсь, моя цель ясна, и подход, описанный выше, не выходит за рамки.


person thanili    schedule 20.10.2015    source источник
comment
Где вы создаете экземпляр GlobalConfig и где вы загружаете FXML, содержащий TableView? Вы можете получить ссылку на контроллер при загрузке FXML, который вам нужно передать в GlobalConfig.   -  person James_D    schedule 20.10.2015
comment
@James_D я только что обновил свой пост запрошенной информацией... спасибо...   -  person thanili    schedule 20.10.2015
comment
Что ж, теперь вы просто создаете новый экземпляр контроллера (через FXMLLoader) вместо того, чтобы использовать тот, который подключен к пользовательскому интерфейсу. Как это будет работать? Если способ, который я показал вам в ответе, не работает, это потому, что в вашем коде есть что-то еще, что вы не показали, что мешает ему работать. Создайте минимальный воспроизводимый пример (с нуля) и отредактируйте свой вопрос, чтобы включить его, вместо того, чтобы публиковать небольшие части вашего проекта.   -  person James_D    schedule 20.10.2015
comment
Ну я думаю, что дело не так сложно. Я просто хочу знать, КАК получить и вызвать мой класс контроллера fxml и его методы из другого класса (не контроллера fxml)...   -  person thanili    schedule 20.10.2015
comment
Передайте ссылку на контроллер (тот, который фактически создается при загрузке и отображении FXML в методе start(), а не какой-то произвольный новый экземпляр контроллера) объекту, которому он нужен. Я уже показал вам в ответе, как это сделать.   -  person James_D    schedule 20.10.2015


Ответы (2)


На пути просто дать GlobalConfig ссылку на контроллер:

public final class GlobalConfig {

    private FXMLDocumentController controller ;

    public void setController(FXMLDocumentController controller) {
        this.controller = controller ;
    }

    ...
    public void addSystemMessage(String messageText, String messageType) {

        SystemMessage msg = new SystemMessage();
        DateTime cur = DateTime.now();

        try {
            msg.setDateSysReceived(cur);
            msg.setMessageText(messageText);
            msg.setMessageType(messageType);
            SystemMessageDAO msgDAO = new SystemMessageDAOImpl();
            msgDAO.addSystemMessage(msg);

            if (controller != null) {
                controller.onAddSystemMessage(msg);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

а затем передайте ссылку на контроллер при создании GlobalConfig:

public void initialize(URL url, ResourceBundle rb) {
    config = new GlobalConfig();
    config.setController(this));
    ...
    messagesData = FXCollections.observableArrayList();
    messagesData = getAllMessages();
    systemMessages.getItems().setAll(messagesData);
    dateSysReceived.setCellValueFactory(new PropertyValueFactory<>("dateSysReceived"));
    messageText.setCellValueFactory(new PropertyValueFactory<>("messageText"));
    messageType.setCellValueFactory(new PropertyValueFactory<>("messageType"));
...
}

Мне не очень нравится это решение, так как оно вводит зависимость от GlobalConfig к классу контроллера (т.е. вы не можете повторно использовать его, если вы не находитесь в среде, где у вас есть контроллер). Другими словами, здесь слишком много жесткой связи. Лучшим подходом является абстрагирование функциональности от контроллера до обратного вызова, который вы можете представить с помощью Consumer<SystemMesage>:

public final class GlobalConfig {

    private Consumer<SystemMessage> messageProcessor ;

    public void setMessageProcessor(Consumer<SystemMessage> messageProcessor) {
        this.messageProcessor = messageProcessor ;
    }

    ...
    public void addSystemMessage(String messageText, String messageType) {

        SystemMessage msg = new SystemMessage();
        DateTime cur = DateTime.now();

        try {
            msg.setDateSysReceived(cur);
            msg.setMessageText(messageText);
            msg.setMessageType(messageType);
            SystemMessageDAO msgDAO = new SystemMessageDAOImpl();
            msgDAO.addSystemMessage(msg);

            if (messageProcessor != null) {
                messageProcessor.accept(msg);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

и тогда вы можете сделать

public void initialize(URL url, ResourceBundle rb) {
    config = new GlobalConfig();
    config.setMessageProcessor(this::onAddSystemMessage);
    ...
    messagesData = FXCollections.observableArrayList();
    messagesData = getAllMessages();
    systemMessages.getItems().setAll(messagesData);
    dateSysReceived.setCellValueFactory(new PropertyValueFactory<>("dateSysReceived"));
    messageText.setCellValueFactory(new PropertyValueFactory<>("messageText"));
    messageType.setCellValueFactory(new PropertyValueFactory<>("messageType"));
...
}

Если ваш GlobalConfig работает в фоновом потоке, вам нужно будет обновить пользовательский интерфейс в потоке приложения FX, что вы можете сделать с помощью

config.setMessageProcessor((SystemMessage msg) -> 
    Platform.runLater(() -> onAddSystemMessage(msg));
person James_D    schedule 20.10.2015
comment
Спасибо, James_D ... Реализовал ваше решение, однако я получаю: java.lang.NullPointerException в com.npap.utils.GlobalConfig.addSystemMessage (GlobalConfig.java: XX) ... никогда не попадая в случай IF, потому что messageProcessor имеет значение NULL в GlobalConfig . - person thanili; 20.10.2015
comment
Вы вызвали config.setMessageProcessor(...) в методе initialize() контроллера, как показано? Какая строка выдает исключение нулевого указателя? Я думаю, другой вопрос: когда вызывается addSystemMessage(...)? Если он вызывается из конструктора, вам нужно инициализировать messageProcessor из конструктора. - person James_D; 20.10.2015
comment
Только что обновил свой пост с соответствующей информацией. Я вызываю config.setMessageProcessor() в методе initialize(); Исключение выдается в случае IF, по-видимому, потому, что messageProcessor не инициализирован. Как видите, я вызываю messageProcessor в методе addSystemMessage(). Метод addSystemMessage() вызывается в нескольких частях моего приложения (дополнительные классы, которые работают в фоновом режиме). Я надеюсь в этом есть смысл - person thanili; 20.10.2015
comment
Итак, я думаю, мне придется где-то инициализировать messageProcessor, по-видимому, в конструкторе (?)... - person thanili; 20.10.2015
comment
Как вы получаете ссылку на GlobalConfig из вашего контроллера в другие части вашего приложения? Вам нужно создать минимальный воспроизводимый пример и отредактировать свой вопрос, чтобы включить его, чтобы мы могли видеть, что вы делаете. . - person James_D; 20.10.2015
comment
Пытался быть более конкретным и ясным в том, что я пытаюсь сделать. Предпочел бы не использовать Consumer для отправки сообщения на мой контроллер, а не напрямую вызывать метод is onAddSystemMessage(...) из GlobalConfig().addSystemMessage(...)... надеюсь, что это имеет смысл, и спасибо за ваше время - person thanili; 20.10.2015
comment
@James_D Я последовал вашему ответу, он идеален, но он работает, когда я добавляю статические данные в ссылку на контроллер. - person Menai Ala Eddine - Aladdin; 19.09.2017

Что я сделал для решения этой проблемы, что я не претендую на лучший подход:

1- используйте новый метод в контроллере под названием reload, который загружает объекты из базы данных и добавляет все это в таблицу.

2- вызовите этот метод при инициализации ()

3- передайте контроллер методу addSystemMessage() (или другому методу-оболочке для согласованности) и вызовите reload @ его конец (поэтому это может быть не очень хорошо, так как включает еще один вызов базы данных, но полезно, если вы хотите убедитесь, что данные синхронизированы)

Другие подходы, которые я не пробовал:

1- пусть addSystemMessage() вернет логическое значение, чтобы указать, что сообщение правильно добавлено в БД, и если true добавить новый объект SystemMessage в таблицу изнутри контроллера, используя systemMessages.getItems()addAll()

2- Может быть лучший способ использовать привязку javafx для привязки вашего объекта таблицы пользовательского интерфейса к другому объекту, синхронизированному с базой данных (я не искал об этом, поэтому я не уверен в его возможности)

person osama yaccoub    schedule 20.10.2015
comment
Просто попытался как-то следовать вашему предложению, используя FXMLLoader из моего второго класса и вызывая метод моего контроллера. Однако я получаю исключение нулевого указателя. Итак, я думаю, я не могу получить объект контроллера fxml из GlobalConfig - person thanili; 20.10.2015