Javafx Using Objects from Maincontroller or Other Controllers in Proper Controller Class

javafx using objects from MainController or other Controllers in proper Controller class

You are creating a controller instance "by hand" with

MainController mc = new MainController();

@FXML-annotated fields are initialized by the FXMLLoader when it creates the controller for you as part of the process of loading the FXML file. Since the controller you created is not the controller instance created by the FXMLLoader, its @FXML-annotated fields are uninitialized (i.e. they are null), and hence you get a null pointer exception.

You can get a reference to the controller created by the FXMLLoader by calling getController() on the FXMLLoader instance after calling load().

If you want one controller to communicate with another, then pass the reference to one controller to the other controller, by defining appropriate methods in the second controller:

public class ConnectionErrorController implements Initializable {

private MainController mainController ;

public void setMainController(MainController mainController) {
this.mainController = mainController ;
}

// ...

@Override
public void initialize(URL location, ResourceBundle resources) {

infoLabel.setText("Connection lost, please try again");
tryButton.setText("try again");
exitButton.setText("exit");

tryButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {

WebEngine webEngine = mainController.getContentPaneController().getVideoWebView().getEngine(); // 1
ToggleButton playButton = mainController.getControlPaneController().getPlayButton(); // 2
if (mainController.testInet()) {
mainController.play(webEngine, playButton);
} else {
// obviously you can now do something better than the "public static field hack" here:
MainController.exist = false;
}
tryButton.getScene().getWindow().hide();
}
});

// ...
}
}

Assuming you are loading the second fxml file in a method in MainController, you can then just do something like:

public class MainController {

// ...

public void showConnectionErrorWindow(String fileName) {

FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/ConnectionError.fxml"));
Parent root = loader.load();
ConnectionErrorController connectionErrorController = loader.getController();
connectionErrorController.setMainController(this);

Scene scene = new Scene(root);
Stage stage = new Stage();
// etc...
}

// ...
}

Note that there are much more elegant ways of solving this problem, such as passing the ConnectionErrorController a callback function (in the form of a lambda expression) to process the call to play(...), which avoid the tight coupling between the ConnectionErrorController and the MainController. However, as you seem to be new to Java, this simpler approach might be more suitable.

See Passing Parameters JavaFX FXML for more information on passing values to controllers.

How to initialize JavaFX controllers with the same model object?

Update

In addition to afterburner.fx, also checkout Gluon Ignite:

Gluon Ignite allows developers to use popular dependency injection frameworks in their JavaFX applications, including inside their FXML controllers. Gluon Ignite creates a common abstraction over several popular dependency injection frameworks (currently Guice, Spring, and Dagger, but we plan at add more as the demand becomes obvious). With full support of JSR-330 Gluon Ignite makes using dependency injection in JavaFX applications trivial.

Injection of model objects into controllers is also via @Inject, similar to afterburner.fx.

Suggested Approach

As you appear to be seeking a dependency injection framework, I think your best option is to use the afterburner.fx framework.

afterburner.fx provides a way injecting model objects into your JavaFX controllers using the standard Java @Inject annotation.

Alternative Dependency Injection Systems

Spring is large and complicated and, unless you need a lot of its other functionality for your application, should not be considered due to its complexity.

Guice is a lot simpler than Spring and a reasonable pick if you need a dependency injection framework with numerous features such as provider classes. But from the sound of it, you don't need all the features that Guice provides as you just want to a way to pass around singleton instances of objects in your application without explicitly looking them up.

So, try out afterburner.fx and see if it fits your needs.

afterburner.fx Sample Code

Here is sample of injecting a model instance (the NotesStore) into a controller using afterburner.fx. The sample is directly copied from the afterburner.fx documentation.

import com.airhacks.afterburner.views.FXMLView;

public class NoteListView extends FXMLView {
//usually nothing to do, FXML and CSS are automatically
//loaded and instantiated
}

public class AirpadPresenter implements Initializable {
@Inject // injected by afterburner, zero configuration required
NotesStore store;

@FXML // injected by FXML
AnchorPane noteList;

@Override
public void initialize(URL url, ResourceBundle rb) {
//view constructed from FXML
NoteListView noteListView = new NoteListView();

//fetching and integrating the view from FXML
Parent view = noteListView.getView();
this.noteList.getChildren().add(view);
}
}

followme.fx is a basic sample application demonstrating how to use afterburner.fx. I did have a few issues getting followme.fx running straight out of the box due to Maven dependency incompatibilities, so I forked it's code and fixed some of the issues which prevented me from using it out of the box.

Answers to addition questions from comments

So from the NoteStore example, are you saying all I have to do is add the afterburner framework dependency and put @Inject on my model variable?

No, you also need to create an associated class that extends FXMLView and instantiate that with a new call (similar to how NotesListView is created in the sample code above). If you are interested in continuing to investigate the afterburner.fx framework, then use the followme.fx project as basis because it provides complete source code for a very simple executable sample using the framework.

I tried google guice and got it to work . . . you'll see in the constructor a game settings object is injected manually.

I don't think you should have to use the Guice injector manually like that. I think you can set a controller factory on an FXMLLoader instance to initiate the injection. This is how the FXMLView in afterburner.fx does it. The exact detail of the injection mechanism used in Guice is going to differ from the afterburner.fx mechanism, but I think the broad concept of setting the controller factory remains similar.

There is a demo of the set controller factory using FXML and Guice in the answer to: Associating FXML and Controller in Guice's Module configuration.

It's a shame there is not a more straightforward way of doing this which does not cause you so many difficulties.

As an inconsequential personal side note, I'm kind of ambivalent on the topic of dependency injection frameworks in general. Sure, they can help, but many times for simple things I'm often OK with a singleton with a getInstance method rather than the more sophisticated framework. Still I do see how in larger projects they can be useful and certainly they are very popular in certain Java frameworks.

Subcontroller loses passed data from MainController

You're creating a new instance of the Controller1 class that is not used with any fxml instead of using the instance used with the included fxml. To inject the controller of a included fxml to a field of the controller for the fxml containing the <fx:include> the name of the field needs to be the fx:id of the <fx:include> element concatenated with "Controller" i.e. in your case

@FXML
private Controller1 tab1Controller;

JAVA and JAVAFX problem - trying to connect additional controllers to the main controller

I don't understand you problem very well, but I try to answer. I think you want to access other controller from main controller,simplest way is:

FXMLLoader mainLoader = new FXMLLoader(getClass().getResource("MainController.fxml"));
Parent main = mainLoader.load();
MainController mainController = mainLoader.getController();

FXMLLoader otherLoader = new FXMLLoader(getClass().getResource("OtherController.fxml"));
Parent other = otherLoader.load();
// set other controller in main controller
mainController.setOtherController(otherLoader.getController());

If you use javafx-weaver and spring boot, DI will make it more easy:

@Component
@FxmlView
class MainController {
@Autowired
private FxControllerAndView<OtherController, VBox> otherControllerAndView;

// otherControllerAndView.getController() to access other controller
}

How to pass parameters to a controller's constructor when using `fx:include`?

After further consideration, I have decided to simply implement Option (3) in my question under "What I've Tried". Basically, instead of using <fx:include>, I decided that it would be easier, more flexible, and more future-proof to include any controllers that require parameters directly/manually from my Java code.


Why?


As I thought more about my issue, I realized that there was absolutely no way for me to pass a parameter to the constructor of my controller "from my FXML". After all, since the object I was trying to pass was defined in my Java code (the main application class), there was not going to be a way to use it in FXML. Thus, my entire perspective on the issue changed.

Option 1

After I re-evaluated my options, I considered just using FXMLLoader.setControllerFactor() after all; this approach was confirmed by James_D in the comments he posted on my question: since all controller instantiation happens through reflection, using reflection to pass my object to the controller wouldn't necessarily be as bad of an idea as I thought. In fact, it would probably work quite well.

However, while this would work well if all I needed to do was pass a single object to the controller, what if, for some of my controller, I wanted to pass multiple objects (and perhaps of different types)? Then the controller factory would grow rather unwieldy as I would have to check for multiple parameters of potentially different types, and then pass the correct ones.

In addition, what if I wanted to pass an object to a controller, not from the application class, where the controller factory is defined, but from somewhere else? For example, imagine that I had an application class, MyApplication.java, a "main" controller, MainController.java, and a controller "under" the main one, NavigationBarController.java? Perhaps I, at some point, would want to pass an object, not from MyApplication to NavigationBarController, but from MainController to NavigationBarController. If that was the case (which is quite possible), then my controller factory would no longer work because it would not have access to the necessary object. Thus, I concluded that setControllerFactory() would not work for me, at least not very well.

Option 2 (What I Used)

The other option (and the one I ended up using) was to include my controllers from my Java code, using FXMLLoader directly; and, rather then including all the controllers from the main application class, I could include each one in the controller that it "resided" in.

For example, using the example provided in Option (1) above, rather than using a controller factory and an fx:controller attribute in the FXML file, I would remove the fx:controller attribute from any FXML file/controller pair that needed access to my object.

Instead, in MyApplication I would use FXMLLoader directly to load, initialize, and add MainController. Then, in MainController, I would use FXMLLoader to include NavigationBarController.

Now, the biggest potential weakness with this approach is the fact that I would need to basically repeat the loading code every time I wanted to include an FXML file/controller pair. To solve this, I created a sort of "utility class" called FXMLQuickLoader that contains methods to easily and quickly load FXML file/controller pairs, but with custom controller objects. If you are interested in seeing the code, I created a GitHub Gist containing FXMLQuickLoader.java.

Option 3 (Untested)

Now, James_D pointed out that I could potentially use something called a "dependency injection factory". If I understood him correctly, it would automatically initialize my controllers' fields, rather than me needing to pass objects into constructors. This solution seems like it would work perfectly; however, because this is my first JavaFX project, and because I am under some time constraints, I decided that it would likely take too long to learn it.

However, for my next JavaFX project, I will probably look into it and (strongly) consider using it. I will try to update this answer later if I do.


Summary


In summary, for me, I determined that it would be best to include my controllers manually using FXMLLoader, rather than trying to use one of the alternatives. However, I am just learning JavaFX, and I am not exactly knowledgeable on the subject, so you should take this answer with a grain of salt. It may help other users who come along, though, and at least outline some potential solutions. I am also still open to further suggestions about the issue.

JavaFXML reference to Controller from all classes

You question is very unclear, because you refer to "other classes" but give absolutely no indication as to what those classes are, or where you instantiate them. I will try to answer anyway, covering all possibilities.

Since the start() method is the entry point to the entire application, the only places you can be creating any other objects are:

  1. In the controller itself, either in the initialize() method, or in an event handler
  2. In the start() method
  3. From objects you create from 1. or 2., or from objects you create from those, etc.

If you are creating those other objects in the controller itself, then you just pass a reference to them, e.g.

@FXML
private void someHandlerMethod(ActionEvent event) {
SomeOtherClass someObject = new SomeOtherClass();
someObject.setController(this);
// ...
}

If you are creating those other objects in the start() method, you can get the controller instance from the FXMLLoader, and pass it to the other objects:

public void start(Stage stage) throws IOException {
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent t) {
Platform.exit();
System.exit(0);
}
});

FXMLLoader loader = new FXMLLoader(getClass().getResource("NavigationView.fxml"));
Parent root = loader.load();

MyController controller = loader.getController();
SomeOtherClass someOtherObject = new SomeOtherClass();
someOtherObject.setController(controller);

Scene scene = new Scene(root);
stage.setResizable(false);
stage.setScene(scene);
stage.show();
stage.setTitle("Greenhouse");
}

In either case, just define the appropriate setter methods in the other class(es):

public class SomeOtherClass {

private MyController controller ;

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

// ...
}

In the third case, you can just recursively do the same thing; pass the controller reference to one object, and then pass it from that object to whoever needs it, etc.

A cleaner approach is probably to use a MVC approach, and share a model instance with all the controllers and other objects that need to modify the application state. See, perhaps, Applying MVC With JavaFx. For moderately complex applications, you might want to consider a dependency-injection framework such as afterburner.fx (which is JavaFX-specific) or Spring or Guice (which are not) to make it easier to "inject" the model where it is needed.



Related Topics



Leave a reply



Submit