1

I am trying to update a javafx tableview defined in my fxml controller by calling a particular method FXMLDocumentController.onAddSystemMessage() from another application utility class method GlobalConfig.addSystemMessage().

Here is my main Application class where i load the 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();
    }

So here are some parts of 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)
    }

Here is also my utility class with a method for adding a system message to database. Additionally I would like to call FXMLDocumentController.onAddSystemMessage(...) method to update tableview with a new item:

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 is a utility class which have some methods for fetching parameters from db as also doing some jobs such as adding some new values to db tables. It is called from several parts of my application and i would like to grab current FXMLDocumentController object and call its method onAddSystemMessage() for updating UI.

Above implementation is according to: Accessing FXML controller class however i am getting a:

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)

Hope my objective is clear and the approach above is not out of scope.

Community
  • 1
  • 1
thanili
  • 777
  • 4
  • 26
  • 57
  • Where are you instantiating `GlobalConfig`, and where are you loading the FXML containing the `TableView`? You can get a reference to the controller when you load the FXML, which you need to pass to `GlobalConfig`. – James_D Oct 20 '15 at 11:35
  • @James_D i have just updated my post with the requested info... thanks... – thanili Oct 20 '15 at 11:50
  • Well now you are just creating a new controller instance (via the `FXMLLoader`), instead of using the one that's attached to the UI. How is that going to work? If the way I showed you in the answer isn't working, it's because there's something else in your code that you haven't shown that is preventing it from working. Create a [MCVE] (from scratch) and [edit] your question to include it, instead of posting small portions of your project. – James_D Oct 20 '15 at 14:23
  • Well i think that the case is not so complicated. I just want to know HOW to get and call my fxml controller class and its methods from another class (not fxml controller) ... – thanili Oct 20 '15 at 14:26
  • Pass a reference to the controller (the one that is actually created when you load and display the FXML in the `start()` method, not some arbitrary new controller instance) to the object that needs it. I already showed you in the answer how to do that. – James_D Oct 20 '15 at 14:29

2 Answers2

2

On way is simply to give the GlobalConfig a reference to the controller:

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();
        }
    }

}

and then pass the reference to the controller when you create the 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"));
...
}

I don't really like this solution too much, as it introduces a dependency from GlobalConfig to the controller class (i.e. you can't reuse it unless you are in an environment where you have a controller). In other words, there's too much tight coupling here. A better approach is to abstract out the functionality from the controller to a callback, which you can represent with a 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();
        }
    }

}

and then you can do

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"));
...
}

If your GlobalConfig is running in a background thread, you will need to update the UI on the FX Application Thread, which you can do with

config.setMessageProcessor((SystemMessage msg) -> 
    Platform.runLater(() -> onAddSystemMessage(msg));
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks James_D ... Have implemented your solution however i am getting: java.lang.NullPointerException at com.npap.utils.GlobalConfig.addSystemMessage(GlobalConfig.java:XX) ... never getting into IF case because messageProcessor is NULL in GlobalConfig. – thanili Oct 20 '15 at 12:40
  • You called `config.setMessageProcessor(...)` in the controller's `initialize()` method as shown? Which line is giving the null pointer exception? I guess the other question is: when is `addSystemMessage(...)` called? If it's called from the constructor you need to initialize `messageProcessor` from the constructor. – James_D Oct 20 '15 at 12:42
  • Just updated my post with the relevant info. I am calling config.setMessageProcessor() within initialize() method; The exception is thrown in the IF case apparently because messageProcessor is not initialized. As you can see i am calling the messageProcessor within addSystemMessage() method. addSystemMessage() method is called in several parts of my application (additional classes which run in the background). I hope this makes sense – thanili Oct 20 '15 at 13:07
  • So i guees i will have to initialize somewhere the messageProcessor, apparently in the costructor(?)... – thanili Oct 20 '15 at 13:17
  • How are you getting the reference to `GlobalConfig` from your controller to other parts of your application? You need to create a [MCVE] and edit your question to include it, so we can see what you are doing. – James_D Oct 20 '15 at 13:49
  • Tried to be more specific and clear on what i am trying to do. Would prefer not to use Consumer for posting msg to my controller rather than directly calling is method onAddSystemMessage(...) from GlobalConfig().addSystemMessage(...) ... hope that makes sense and thanks for your time – thanili Oct 20 '15 at 14:18
  • @James_D I followed your answer ,it is perfect but it is worked when i add static to controller reference. – Menai Ala Eddine - Aladdin Sep 19 '17 at 03:43
1

What I did to solve this issue -Which I don't claim to be the best approach- :

1- avail a new method in the controller called reload, which loads the entities from the database and add all of it to the table.

2- call this method on initialize()

3- pass the controller to the addSystemMessage() method (or other wrapper method for coherence) and call reload @ its end (that is why it might not very good as it involves another call to the database, but it's useful f you want to be sure that data is synchronized)

Other approaches that I didn't try :

1- let addSystemMessage() return boolean to indicate the message is added correctly to the DB, and if true add the new SystemMessage object to the table from inside the controller using systemMessages.getItems()addAll()

2- There might be a better way using javafx binding to bind your UI table object with other object synchronized with the database (I didn't search about that so I am not sure about its feasability)

osama yaccoub
  • 1,884
  • 2
  • 17
  • 47
  • Just tried to follow somehow your suggestion my using FXMLLoader from my second class and calling my controller's method. However i am getting a null pointer exception. So i guess i can not fetch fxml controller object from GlobalConfig – thanili Oct 20 '15 at 14:19