3

I'm going crazy with the making of an application in Java/JavaFX.

I have a project with more fxml files, each with a controller (exactly like this example JavaFX TabPane - One controller for each tab )

This is the main fxml file (screentab.fxml), with multiple fx:include.

<TabPane fx:id="tabPane" BorderPane.alignment="CENTER">
    <tabs>
     <Tab text="Studenti">
           <content>
           <fx:include fx:id="Studenti" source="tabStudenti.fxml" />
            </content>
           </Tab>
      <Tab text="Percorsi &#10;formativi">
      <content>
      <fx:include fx:id="tabPercorsiFormativi" source="tabPercorsiFormativi.fxml" />
      </content>
           </Tab>
        <Tab text="Calendario &#10;delle&#10;lezioni">
          <content>
         <fx:include fx:id="tabCalendario" source="tabCalendario.fxml" />   
          </content>
           </Tab>
    </tabs>
       </TabPane>

It works, but I have a problem with the model, I think.

Below the file Main.java

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Manage your student");
        Model model = new Model();
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("gui/screentab.fxml"));
            BorderPane root = (BorderPane)loader.load();
            SoftwareController controller = loader.getController();
            controller.setModel(model);
            Scene scene = new Scene(root,1000,600);
            scene.getStylesheets().add(getClass().getResource("gui/stylesheet1.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

This is the main controller (SoftwareController.java) with the method setModel();

public class SoftwareController {

    private Model model;

    @FXML private TabPane tabPane;

    @FXML private tabCalendarioController tabCalendarioController;
    @FXML private tabPercorsiFormativiController tabPercorsiFormativiController;
    @FXML private tabStudentiController tabStudentiController;


    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        tabStudentiController.init(this);
        tabCalendarioController.init(this);
        tabPercorsiFormativiController.init(this);
    }

}

The model call the DAO and contains all the methods, called from the multiple controllers.

public class Model {

    StudentiDAO dao = new StudentiDAO();
    List<Studente> elencoStudenti = new ArrayList<Studente>();

    public List<Studente> elencaStudenti(){
        elencoStudenti= dao.listaStudenti();
        return elencoStudenti;

    }
}

And this is one of the controllers, that call the method of the model.

public class tabStudentiController {
    private SoftwareController main;
    private Model model;

    @FXML
    private Button btnElencoStudenti;

    @FXML
    public void doVisualizzaStudenti(ActionEvent event) {

        model.elencaStudenti();

        txtStudenti.appendText("Elenco studenti: \n");
        for(Studente s: lista ){

            txtStudenti.appendText(s.getStud_NOME()+ " "+ s.getStud_COGNOME()+ "\n");
        } 

    }

 public void setModel(Model model) {
            this.model = model ;
        }

    public void init(SoftwareController softwareController) {
    main = softwareController;

}


    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        assert tabStudenti != null : "fx:id=\"tabStudenti\" was not injected: check your FXML file 'screentab.fxml'.";
    }

}

The problem: when I press the button that does start the event doVisualizzaStudenti i have this error:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(Unknown Source)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.Node.fireEvent(Unknown Source)
at javafx.scene.control.Button.fire(Unknown Source)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(Unknown Source)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)


at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
    at javafx.event.Event.fireEvent(Unknown Source)
    at javafx.scene.Scene$MouseHandler.process(Unknown Source)
    at javafx.scene.Scene$MouseHandler.access$1500(Unknown Source)
    at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.notifyMouse(Unknown Source)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.Trampoline.invoke(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
    ... 57 more
Caused by: java.lang.NullPointerException
    at software.tabStudentiController.doVisualizzaStudenti(tabStudentiController.java:36)
    ... 66 more

where tabStudentiController.java:36 is the line with model.elencaStudenti().

Where I'm doing wrong? Please, help me and sorry for my bad english!

I changed the main with the callback and I added the setModel() in each nested controller. But now I have this error:

`javafx.fxml.LoadException: 
/C:/software/gui/screentab.fxml
at javafx.fxml.FXMLLoader.constructLoadException(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.load(Unknown Source)
at software.Main.start(Main.java:43)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.Trampoline.invoke(Unknown Source)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
... 13 more
Caused by: java.lang.NullPointerException
at software.SoftwareController.initialize(SoftwareController.java:42)
... 22 more`

where Main.java:43 is the line BorderPane root = (BorderPane)loader.load() and SoftwareController.java:42 is: tabStudentiController.init(this);

What I'm wrong again?

Community
  • 1
  • 1
silvia-ma
  • 33
  • 1
  • 4
  • Can you show the `init(...)` method in `tabStudentiController` that is called from `SoftwareController.initialize()`? Where is `model` initialized in `tabStudentiController`? – James_D Mar 08 '16 at 01:09
  • Sure, sorry. Initially I wrote this but then I deleted it because it did not work. I added the missing lines of code in the initial post, in the `tabStudentiController`. – silvia-ma Mar 08 '16 at 08:49

3 Answers3

1

I don't see anywhere where you call setModel(...) for the "nested controllers" (e.g. for tabStudentiController). Since you never initialize the model, you get a null pointer exception at model.elencaStudenti().

You probably want to initialize each nested controller's model when you set the model on SoftwareController:

public class SoftwareController {

    private Model model;

    @FXML private TabPane tabPane;

    @FXML private tabCalendarioController tabCalendarioController;
    @FXML private tabPercorsiFormativiController tabPercorsiFormativiController;
    @FXML private tabStudentiController tabStudentiController;


    public void setModel(Model model) {
        this.model = model;
        tabStduentiController.setModel(model);
        tabCalendarioController.setModel(model);
        tabPercorsiFormativiController.setModel(model);
    }

    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        tabStudentiController.init(this);
        tabCalendarioController.init(this);
        tabPercorsiFormativiController.init(this);
    }

}

Another approach

Perhaps a cleaner way to do this is to make all your controller classes take a reference to a model as a constructor parameter. That way, you are guaranteed that each controller has a model instance as soon as it is created:

public class SoftwareController {

    private Model model;

    @FXML private TabPane tabPane;

    @FXML private tabCalendarioController tabCalendarioController;
    @FXML private tabPercorsiFormativiController tabPercorsiFormativiController;
    @FXML private tabStudentiController tabStudentiController;

    public SoftwareController(Model model) {
        this.model = model ;
    }

    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        tabStudentiController.init(this);
        tabCalendarioController.init(this);
        tabPercorsiFormativiController.init(this);
    }

}

and

public class tabStudentiController {
    private SoftwareController main;
    private Model model;

    @FXML
    private Button btnElencoStudenti;

    public tabStudentiController(Model model) {
        this.model = model ;
    }

    @FXML
    public void doVisualizzaStudenti(ActionEvent event) {

        model.elencaStudenti();

        txtStudenti.appendText("Elenco studenti: \n");
        for(Studente s: lista ){

            txtStudenti.appendText(s.getStud_NOME()+ " "+ s.getStud_COGNOME()+ "\n");
        } 

    }


    public void init(SoftwareController softwareController) {
        main = softwareController;

    }


    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        assert tabStudenti != null : "fx:id=\"tabStudenti\" was not injected: check your FXML file 'screentab.fxml'.";
    }

}

and similarly for the other controllers.

By default, the FXMLLoader calls the default (no-argument) constructor of the controller class to create the controller instance. Since you no longer have such a constructor, you need to tell the FXMLLoader how to create controller instances, which you can do with a controller factory:

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Manage your student");
        Model model = new Model();

        // the controller factory is just a function mapping a Class 
        // to a controller instance:

        Callback<Class<?>, Object> controllerFactory = type -> {
            try {
                for (Constructor<?> c : type.getConstructors()) {
                    // look for a constructor taking a single parameter of type Model:
                    if (c.getParameterCount()==1 && c.getParameterTypes()[0]==Model.class) {
                        return c.newInstance(model);
                    }
                }
                // no suitable constructor found, just use default:
                return type.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("gui/screentab.fxml"));
            loader.setControllerFactory(controllerFactory);
            BorderPane root = (BorderPane)loader.load();
            Scene scene = new Scene(root,1000,600);
            scene.getStylesheets().add(getClass().getResource("gui/stylesheet1.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
fabian
  • 80,457
  • 12
  • 86
  • 114
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks a lot for your answers, but it doesn't work yet! I have an error on `BorderPane root = (BorderPane)loader.load();` (I update the first post with the new error.) – silvia-ma Mar 09 '16 at 09:02
  • You just have the `fx:id` not matching the controller name. You need `` instead of ``. – James_D Mar 09 '16 at 09:18
0

This is one of the few examples where a controllerFactory is of use. This is a factory responsible for creating the controller instances, which can be used to your advantage here. Add a interface to all controllers that need access to the Model and pass the model to the controller instance after creation using a method in that interface.

Benefits over other methods described in Passing Parameters JavaFX FXML

  • You do not need to pass the Model through the controller hierarchy.
  • You can decide on the controller used in the fxml file
  • The Model instance is available when initialize is called.

Example

(Everything inside the modelinjection package)

public class ModelInjectionControllerFactory implements Callback <Class<?>, Object> {

    private final Model model;

    public ModelInjectionControllerFactory(Model model) {
        this.model = model;
    }

    @Override
    public Object call(Class<?> param) {
        try {
            // create controller using default constructor
            Object controller = param.newInstance();

            // inject model, if needed
            if (controller instanceof ModelInjectionTarget) {
                ((ModelInjectionTarget) controller).injectModel(model);
            }
            return controller;
        } catch (IllegalAccessException | InstantiationException ex) {
            throw new IllegalArgumentException("Could not initialize "+ param.getSimpleName()+" using the default constructor", ex);
        }
    }

}
public interface ModelInjectionTarget {

    void injectModel(Model model);

}

public class Model {

    @Override
    public String toString() {
        return "I am your model";
    }

}
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="modelinjection.ParentController">
    <fx:include source="child.fxml"/>
</AnchorPane>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="modelinjection.ChildController">
</AnchorPane>
public class ParentController {
}

public class ChildController implements Initializable, ModelInjectionTarget {

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        System.out.println("this is my model: " +model);
    }    

    private Model model;

    @Override
    public void injectModel(Model model) {
        this.model = model;
    }

}

Usage

Model model = new Model();
ModelInjectionControllerFactory controllerFactory = new ModelInjectionControllerFactory(model);

FXMLLoader loader = new FXMLLoader(ModelInjectionControllerFactory.class.getResource("parent.fxml"));
loader.setControllerFactory(controllerFactory);

Scene scene = new Scene(loader.load());
Community
  • 1
  • 1
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Perhaps even better: use an annotation on the model field (e.g. `@Inject private Model model ;`) instead of forcing the controller to implement an interface... That's basically the way afterburner.fx works. This is barely different to my answer, though? – James_D Mar 08 '16 at 13:56
  • @James_D: A new annotation or a javaEE one? Personally I'd go with `@FXML` since the purpose is similar enough, that is if i wanted to use reflection to pass the `Model`. – fabian Mar 08 '16 at 14:08
  • 1
    Either a new one or `javax.inject.Inject`. You could use `@FXML` but it might get confusing as you'd then expect to see the model defined as an FXML element (and conceivably you might want to support that use separately). Or, of course, just use afterburner.fx, which already implements all this. – James_D Mar 08 '16 at 15:43
0

The error arises because you have not instantiated your model. The easiest way to fix this problem is to make the Model Singleton

public class Model {

    private static final Model model;
    StudentiDAO dao;
    List<Studente> elencoStudenti;

    private Model(){
        dao = new StudentiDAO();
        elencoStudenti = new ArrayList<Studente>();
    }

    public static Model getInstance(){
        if(model == null){
            model = new Model();
        }

        return model;
    }

    public List<Studente> elencaStudenti(){
        elencoStudenti= dao.listaStudenti();
        return elencoStudenti;
    }
}

You can also provide getter and setter methods for every variable. If you now want to use the Model you just need to call

Model model = Model.getInstance();

and can afterward access all variables through your methods

model.elencaStudenti();

This is in my opinion a better solution instead of pulling the model through every class. When using a singleton you model is accesable from everywhere. My application have more than 50 controller in total and this works pretty well. In the model I also use PropertyBindings which is quiet handy. I think this approach also complies with MVC idea.

Westranger
  • 1,308
  • 19
  • 29