How can I access javafx Application class from a Controller class? If I go into more specific I need to keep one stage and switch scenes.
You can call the following on an arbitrary node of your scene to get the current stage.
Node.getScene().getWindow()
It will give you an object from type Window. (Stage subclasses Window)
Or you hand over the stage from outside of the controller:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
"Main.fxml"));
fxmlLoader.setRoot(this);
MainController controller = new MainController()
controller.setStage(stage);
fxmlLoader.setController(controller);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
Related
I am developing a software using JavaFX.
It is a desktop application.
I am new to MVC, and also JavaFX; But learn some details by googling.
I follow the steps here, to learn about JavaFX and also MVC.
I know that in MVC, Model is a POJO, View is visualization, and controller acts on both, accept input and convert to commands for view and model.
The model can also have logic to update the controller.
We also for each view, should have a controller.( view-controllers)
But I have some question; Why in the tutorial , We create both PersonEditDialog and PersonOverview stage in the mainApp?
I mean this :
public void showPersonOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
AnchorPane personOverview = (AnchorPane) loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
// Give the controller access to the main app.
PersonOverviewController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
And this :
public boolean showPersonEditDialog(Person person) {
try {
// Load the fxml file and create a new stage for the popup dialog.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonEditDialog.fxml"));
AnchorPane page = (AnchorPane) loader.load();
// Create the dialog Stage.
Stage dialogStage = new Stage();
dialogStage.setTitle("Edit Person");
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(primaryStage);
Scene scene = new Scene(page);
dialogStage.setScene(scene);
// Set the person into the controller.
PersonEditDialogController controller = loader.getController();
controller.setDialogStage(dialogStage);
controller.setPerson(person);
// Show the dialog and wait until the user closes it
dialogStage.showAndWait();
return controller.isOkClicked();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
Both are defined and created in the mainApp class?
Why we did not create the PersonEditDialog stage and scene in the PersonOverviewController?
In controllers we should not use new keyword? They just are connectores between view and model?
Im asking about controller rules; for multi stage and .fxml software.
My software includes 8 different pages( or more ), Should I create all stages in the mainApp ?
Why should not create new stage and add the according .fxml to the scene of that stage in previous step of the application?
It's not possible to do what you are saying, you can't create something from some other thing that's not even existing. If you don't instantiate the PersonOverview in Main, you can't make it do anything.
Also, notice in this case that the view of PersonOverview is attached to the RootLayout which is created in Main. So you can consider these as one Main View where each controller is managing one part of the view.
In the case of PersonEditDialog, You are starting a stage through the main view to edit some information. That's why it's created in Main. The stage is attached to the MainStage.
And if you have several stages to create, You don't have to do that in Main. It depends on your needs. You can basically run a stage which uses some controller from another controller by clicking on a button for example. So it depends on : after what event you want to see that stage.
Example : You can add a button in PersonEditDialog controller like More... and you define its setOnAction event to open a new view (stage) where you show the picture, the Twitter link...
My dashboard fxml location is Dashboard/DashBoardScene.fxml. I tried to switch from Login/LoginController to dashboard screen
public void onLoginButtonClick(ActionEvent actionEvent) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Dashboard/DashBoardScene.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle("ABC");
stage.setScene(new Scene(root1));
stage.show();
}
but i got location is required error?
The code is in a class in the RestarantApp.Dashboard package (BTW, please use proper naming conventions). Your FXML file is in the same package.
The code getClass().getResource(...) will return a URL of a resource that is searched relative to the current class. Since the current class and the FXML file are in the same package, all you need is
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("DashBoardScene.fxml"));
(This assumes the FXML file is being deployed correctly, the resource name is spelled correctly, etc.)
You can also specify an "absolute" path (one that is relative to the classpath) to the resource:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/RestarantApp/Dashboard/DashBoardScene.fxml"));
Note here the path begins with a leading /.
with the ScheneBuilder I defined the controller class of my fxml, the code genereted inside my AnchorPane tag is:
fx:controller="demo.SplashController"
now I would like if I had args in the main, to load a new version of the controller, using the appropriate construct. I try this code in the Application.start:
FXMLLoader loader = new FXMLLoader(getClass().getResource("page.fxml"));
PageController controller;
if(!dir.equals("")){ //attribute coming from args
controller = new PageController(dir);
}else{
controller = new PageController();
}
loader.setController(controller);
AnchorPane root = loader.load();
Scene scene = new Scene(root,480,414);
primaryStage.setScene(scene);
primaryStage.show();
but using this code a conflict appears because I have already defined the controller in my project with FXML code, to solve it would be enough to remove the segment in the FXML code but I would not do it because leaving the code in the fxml allows me to access some good features of the SceneBuilder.
The only way pass parameters to the controller's constructor and specify the controller's class in the fxml is to use a controller factory:
FXMLLoader loader = new FXMLLoader(getClass().getResource("page.fxml"));
loader.setControllerFactory(cl -> dir.isEmpty() ? new PageController() : new PageController(dir));
AnchorPane root = loader.load();
Another option would be to create a method in the controller class that allows you to pass the info after loading and does the initialisation:
FXMLLoader loader = new FXMLLoader(getClass().getResource("page.fxml"));
AnchorPane root = loader.load();
PageController controller = loader.getController();
controller.setDir(dir);
Note that the method call happens after the initialize method is run assuming there is one.
Is there a way to trigger a function in a controller, when the stage containing the view connected to the controller is closed? Let’s say I want to call a “cleanup” function (e.g. save changes) on every controller in my stage, when the window is closed.
Typically you put the content of the FXML file into a stage (via a scene) externally to the FXML and controller. So you should add code where you actually have access to the stage to do this. You can define a method in your controller class to invoke when you need to do the "cleanup":
public class Controller {
// injected fields, etc...
public void initialize() {
// initialization code...
}
// event handlers, etc...
public void shutdown() {
// cleanup code here...
}
}
Now when you load the FXML and display its content in a window, you can register a handler with the stage that invokes the controller's shutdown method:
Stage stage = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file"));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
// cleanup controller resources when window closes:
Controller controller = loader.getController();
stage.setOnHidden(e -> controller.shutdown());
stage.show();
You can use Window.setOnHidden
I am having the following problem with a program that I am currently writing, and I have searched on the internet, but I couldn't really find anything to help me understand the following problem
So inside another class I have written a method that executes this whenever the search button is clicked and the method looks like this:
public void searchButton(){
try {
new SearchController().display();
} catch (IOException e) {
e.printStackTrace();
}
}
And then the SearchController class looks something like this (I simplified it here):
public class SearchController {
#FXML
private Button cancelButton;
#FXML
private Label what;
private static Stage stage;
private static BorderPane borderPane;
#FXML
public void initialize(){
what.setText("Testing"); // this woks
cancelButton.setOnAction(e -> stage.close());
}
public void display() throws IOException {
stage = new Stage();
stage.setResizable(false);
stage.setTitle("Product search");
stage.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(SearchController.class.getResource("Search.fxml"));
borderPane = loader.load();
Scene scene = new Scene(borderPane);
stage.setScene(scene);
//what.setText("Testing") and this doesn't work
stage.showAndWait();
}
}
Can someone please tell me why it is possible to write text on the initialize method (that method gets called after the borderPane = loader.load(); line...so why doesn't it work if I try to write on the label after that line?)
Thank you in advance
The FXMLLoader creates an instance of the class specified in the fx:controller attribute of the FXML root element. It then injects the elements defined in the FXML file into the controller instance it created when the fx:id attributes match the field names. Then it calls the initialize() method on that instance.
You create an instance of the controller "by hand" with new SearchController(). This is not the same object that is created by the FXMLLoader. So now when you have loaded the fxml file you have two different instances of SearchController. So if you call what.setText(...) from the display() method, you are not calling it on the controller instance created by the FXMLLoader. Consequently, what has not been initialized in the instance on which you are calling what.setText(...), and you get a null pointer exception.
Since initialize() is invoked by the FXMLLoader on the instance it created, when you call what.setText(...) from the initialize() method, you are calling it on the instance created by the FXMLLoader, and so the FXML-injected fields for that instance have been initialized.