I am trying to build my first 'real' application with java using JavaFX. I'm using FXML to design the whole thing and so I decided to divide up some parts into different .fxml files and link those to different controllers. I'm using one MainController linked to main.fxml, and within main.fxml I call some other fxml files using fx:include. All includes have their own controller.
The issue is that the controllers don't operate 100% independently, sometimes you want to press a button linked to one controller and have that so something to another controller. I have a solution that works, but I'm not sure if it is the best way to do it.
My solution is this: Have one abstract SubController class, which has protected static fields for each of the subcontrollers. Upon initialization MainController fills all of these fields with the controller classes it gets from main.fxml, as well as one field for MainController itself. Each of the sub-controllers inherit from SubController so that they can access the static fields and call the public methods of the other controllers.
public abstract class SubController implements Initializable {
protected static MainController mainController;
protected static MenuBarController menuBarController;
protected static VideoPanelController videoPanelController;
public static void setMainController(MainController mainController) {
SubController.mainController = mainController;
}
public static void setMenuBarController(MenuBarController menuBarController) {
SubController.menuBarController = menuBarController;
}
public static void setVideoPanelController(VideoPanelController videoPanelController) {
SubController.videoPanelController = videoPanelController;
}
}
public class MainController implements Initializable {
#FXML private MenuBarController menuBarController;
#FXML private VideoPanelController videoPanelController;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
SubController.setMainController(this);
SubController.setMenuBarController(menuBarController);
SubController.setVideoPanelController(videoPanelController);
}
This way when a button is clicked in the menu bar, it can use a public method of the video panel controller:
public class MenuBarController extends SubController {
public void openVideo() {
File selectedFile = chooseFiles(VIDEO_EXTENSIONS);
if (selectedFile == null) return;
videoPanelController.loadVideo(selectedFile);
}
}
This works quite well, but I still feel like there's probably an easier way to do it. Is this a good way to go about it and if not, how else should I do it?
Related
I just realized i have two initalize methods that are in my controller class.
One is created by Scene Builder and makes no requirement on class to implement Initializable interface.
#FXML
void initialize()
{
assert birthDate != null
: "fx:id=\"birthDate\" was not injected: check your FXML file 'baptismEntryView.fxml'.";
assert baptismDate != null
: "fx:id=\"baptismDate\" was not injected: check your FXML file 'baptismEntryView.fxml'.";
...
}
Other one with two parameters needs to implement Initalizable interface
#Override
public void initialize(URL location, ResourceBundle resources)
{
...
}
I see that there is difference, that initialize that requires to implement Initializable interface allows me to access ResourceBundle during creation of controller for example.
What i want to ask is in what order they are called and what everything I am supposed to do in them (maybe some docs/tutorial reference?).
You should not have both. I can't find documentation on this, but it appears that if the controller implements Initializable then the no-arg initialize() method is never invoked.
It's actually recommended you don't implement Initializable:
NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.
If you still need access to the ResourceBundle (and/or location URL) then you can do the following:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
public class Controller {
// you don't need to declare both 'location' and 'resources'
#FXML private URL location;
#FXML private ResourceBundle resources;
#FXML
private void initialize() {
// ...
}
}
My Application class looks like this:
public class Test extends Application {
private static Logger logger = LogManager.getRootLogger();
#Override
public void start(Stage primaryStage) throws Exception {
String resourcePath = "/resources/fxml/MainView.fxml";
URL location = getClass().getResource(resourcePath);
FXMLLoader fxmlLoader = new FXMLLoader(location);
Scene scene = new Scene(fxmlLoader.load(), 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The FXMLLoader creates an instance of the corresponding controller (given in the FXML file via fx:controller) by invoking first the default constructor and then the initialize method:
public class MainViewController {
public MainViewController() {
System.out.println("first");
}
#FXML
public void initialize() {
System.out.println("second");
}
}
The output is:
first
second
So, why does the initialize method exist? What is the difference between using a constructor or the initialize method to initialize the controller required things?
Thanks for your suggestions!
In a few words: The constructor is called first, then any #FXML annotated fields are populated, then initialize() is called.
This means the constructor does not have access to #FXML fields referring to components defined in the .fxml file, while initialize() does have access to them.
Quoting from the Introduction to FXML:
[...] the controller can define an initialize() method, which will be called once on an implementing controller when the contents of its associated document have been completely loaded [...] This allows the implementing class to perform any necessary post-processing on the content.
The initialize method is called after all #FXML annotated members have been injected. Suppose you have a table view you want to populate with data:
class MyController {
#FXML
TableView<MyModel> tableView;
public MyController() {
tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point.
}
#FXML
public void initialize() {
tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all #FXML annotated members.
}
}
In Addition to the above answers, there probably should be noted that there is a legacy way to implement the initialization. There is an interface called Initializable from the fxml library.
import javafx.fxml.Initializable;
class MyController implements Initializable {
#FXML private TableView<MyModel> tableView;
#Override
public void initialize(URL location, ResourceBundle resources) {
tableView.getItems().addAll(getDataFromSource());
}
}
Parameters:
location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized.
And the note of the docs why the simple way of using #FXML public void initialize() works:
NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.
Was marked as duplicate, but still: my question does not refer to override annotations in general, but specifically in fxml controllers.
I am pretty new to java and trying to build a fxml application. I want to add an #Override annotation to a java fxml controller, but I am getting the error message "error: method does not override or implement a method from a supertype", and I cannot figure the origin of the mistake.
The same annotation can be used without any problem to the main java document, so I don't why it is not possible in the controller. The function only is effective once used with the #Override annotation, so commenting it out makes the function useless.
Is is impossible to use additional #Overrides in the controller, or is it a matter of handling them correctly?
public class Main_FXMLController implements Initializable {
private Button button;
#FXML
private void handleButtonAction(ActionEvent event) throws IOException {
System.out.println("You clicked me!");
Platform.exit();
}
// I am trying to add to #Override annotation here, but it returns " method does not override or implement a method from a supertype"
#Override
private static void test () {
System.out.println("Test");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
My Application class looks like this:
public class Test extends Application {
private static Logger logger = LogManager.getRootLogger();
#Override
public void start(Stage primaryStage) throws Exception {
String resourcePath = "/resources/fxml/MainView.fxml";
URL location = getClass().getResource(resourcePath);
FXMLLoader fxmlLoader = new FXMLLoader(location);
Scene scene = new Scene(fxmlLoader.load(), 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The FXMLLoader creates an instance of the corresponding controller (given in the FXML file via fx:controller) by invoking first the default constructor and then the initialize method:
public class MainViewController {
public MainViewController() {
System.out.println("first");
}
#FXML
public void initialize() {
System.out.println("second");
}
}
The output is:
first
second
So, why does the initialize method exist? What is the difference between using a constructor or the initialize method to initialize the controller required things?
Thanks for your suggestions!
In a few words: The constructor is called first, then any #FXML annotated fields are populated, then initialize() is called.
This means the constructor does not have access to #FXML fields referring to components defined in the .fxml file, while initialize() does have access to them.
Quoting from the Introduction to FXML:
[...] the controller can define an initialize() method, which will be called once on an implementing controller when the contents of its associated document have been completely loaded [...] This allows the implementing class to perform any necessary post-processing on the content.
The initialize method is called after all #FXML annotated members have been injected. Suppose you have a table view you want to populate with data:
class MyController {
#FXML
TableView<MyModel> tableView;
public MyController() {
tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point.
}
#FXML
public void initialize() {
tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all #FXML annotated members.
}
}
In Addition to the above answers, there probably should be noted that there is a legacy way to implement the initialization. There is an interface called Initializable from the fxml library.
import javafx.fxml.Initializable;
class MyController implements Initializable {
#FXML private TableView<MyModel> tableView;
#Override
public void initialize(URL location, ResourceBundle resources) {
tableView.getItems().addAll(getDataFromSource());
}
}
Parameters:
location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized.
And the note of the docs why the simple way of using #FXML public void initialize() works:
NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.
Given a Play controller MyController with an action myAction, is it possible to call another action without triggering a redirect? Let's say I have another controller:
public class MyController2 extends Controller {
public static void myAction2() throws Exception {
MyController.myAction(); //this will cause a redirect.
}
}
Is it possible to call myAction without triggering a redirect. Note that I am using Play 1.2.x and not Play 2.x.
You can call myAction and not have it redirect. Just change the access level of myAction to anything except public. However, you will not be able to route to myAction directly any more.
If you still need myAction to be routable in it's own right, then I would suggest moving any common functionality into a separate method/class and then calling that method from myAction2 and myAction, like so:
public class Application extends Controller {
public static void myAction() {
commonActionStuff("myAction");
}
public static void myAction2() {
commonActionStuff("myAction2");
}
protected static void commonActionStuff(String whoCalledMe) {
// your common functionality implemented here
renderText(whoCalledMe);
}
}