I've to create an application for a school project. After you login, you should go to the dashboard. That works, but, it throws a NullPointerException when I try to set the button do disabled.
In this file, the stage is changing (after logging in):
public class ScreenController {
public void setScene(Stage stage, Parent root,Button button, String file){
if(file == "dashboard"){
stage = (Stage) button.getScene().getWindow();
try {
root = FXMLLoader.load(getClass().getResource("Dashboard.fxml"));
} catch (IOException ex) {
System.out.println("IOException: "+ex);
}
Scene scene = new Scene(root);
stage.setTitle("Corendon - Dashboard");
stage.setScene(scene);
stage.show();
Dashboard dashboard = new Dashboard();
}
}
}
And on the last line, the button should be set to disabled...
Dashboard dashboard = new Dashboard();
... by this class:
public class Dashboard extends ScreenController {
#FXML public Button buttonDashboard1;
public Dashboard(){
buttonDashboard1.setDisable(true);
}
}
But that is not working it throws the NullPointerException:
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
at
...
Caused by: java.lang.NullPointerException
at fastenyourseatbelts.Dashboard.<init>(Dashboard.java:11)
at fastenyourseatbelts.ScreenController.setScene(ScreenController.java:33)
at fastenyourseatbelts.LoginController.buttonLogin(LoginController.java:74)
... 59 more
I'm trying for hours now, but I don't get the solution... Does anyone know what is going wrong and why?
Move the code from the constructor of your controller to the initialize. This method is invoked by the FXMLLoader, after injection of all fields is completed and therefore should have access to the buttonDashboard1 instance:
public class Dashboard extends ScreenController {
#FXML public Button buttonDashboard1;
#FXML
private void initialize() {
buttonDashboard1.setDisable(true);
}
}
You also have to make sure the controller is either specified in the root element of the fxml file e.g. (replace packagename with the package of the Dashboard class)
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="packagename.Dashboard">
<children>
<Button text="click me" fx:id="buttonDashboard1"/>
</children>
</VBox>
or set as controller for the fxml before loading it:
FXMLLoader loader = new FXMLLoader(getClass().getResource("Dashboard.fxml"));
Dashboard dashboard = new Dashboard();
loader.setController(dashboard);
root = loader.load();
(don't use a fx:controller attribute in this case)
Related
I have a parent controller say HomeController
It has a node SidePanel (JFXDrawer) with SidePanelController and a node anchorPane with varying controller.
HomeController
|
/ \
/ \
/ \
anchorPane SidePanel
(Controller (Controller = SidePanelController)
= varies)
The anchorPane node should load multiple fxml views with menu buttons clicked from SidePanelController.
The problem here is in SidePanelController since the buttons are inside it, I cannot directly load onanchorPane as for SidePanelController the node anchorPane does not exists
This question seems duplicate of this but its not because the parent controller is waiting for scene to close so it fetches back the data to parent controller. But in my case, every time I click on menu button, it will load a view accordingly.
Can anybody provide resources for making controller for JFXDrawer (as child node).
If say, I have a side navigation drawer sidepanel.fxml like this
And I have a HomeScreen like this
So by using following code, I stacked drawer in my homecontroller
try {
SidePanelController controller = new SidePanelController();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sidepanel.fxml"));
loader.setController(controller);
Parent root = loader.load();
drawer.setSidePane(root);
} catch (IOException err) {
err.printStackTrace();
}
Finally, I will be getting this as combined output
Now what I want is whenever I try to click on say Lorry Receipt button on Side Navigation Panel, it should trigger the node residing on Parent controller. Even the event which will pass data back to parent controller without closing the child node will work.
As #Sedrick suggested, I initialized events on all buttons of SidePanelController in HomeController (Parent) itself. At first it returned NPE, but later I let the buttons initialize and then fetch it back to parent controller.
So here is the solution. It might be non-ethical, so other alternatives still appreciated.
public class SidePanelController implements Initializable {
#FXML
private JFXButton btn_lr;
#FXML
private JFXButton btn_shipment;
#FXML
private JFXButton btn_add_inward;
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//Your other activities when sidedrawer/pane initializes
}
//After I initialize, I would like all of these buttons to be fetched to my Parent controller. So instead of me passing each button separately, I made a list to save my time.
public ObservableList<JFXButton> fetchAllButtons(){
ObservableList<JFXButton> allbuttons = FXCollections.observableArrayList(btn_lr, btn_add_inward, btn_shipment);
return allbuttons;
}
Now in HomeController or ParentController, I fetch all these buttons and create events for it separately. So here goes the code.
public class HomeController implements Initializable {
#FXML
private JFXHamburger hamburger;
#FXML
private JFXDrawer drawer;
//Creating Hamburger Task to toggle sidebar
HamburgerBackArrowBasicTransition burgerTask;
//Declaring sidepanelcontroller globally because I need it in multiple methods.
SidePanelController controller = new SidePanelController();
//Finally a list to fetch the list of all buttons from SidePanelController
ObservableList<JFXButton> sidebuttons;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//Using hamburger to toggle my side drawer
burgerTask = new HamburgerBackArrowBasicTransition(hamburger);
//Initializing the scene of drawer/SidePanel FXML file on Home.fxml or parent fxml itself
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sidepanel.fxml"));
loader.setController(controller);
Parent root = loader.load();
drawer.setSidePane(root);
sidebuttons = controller.fetchAllButtons();
//Make sure not to declare this before initializing or else it returns NPE
} catch (Exception err) {
err.printStackTrace();
}
//Finally you can declare your tasks for each button by declaring events on each of those buttons. I thought only common but different thing you can between buttons is the name of it. So I used switch statement just to point out each button separately. (I know this might be unethical, but works for me though)
for (JFXButton button: sidebuttons) {
switch (button.getText()){
case "Lorry Receipt":
button.setOnAction(actionEvent -> {
//Your actions here when button with name Lorry Receipt is pressed
});
break;
case "Shipment Memo":
button.setOnAction(actionEvent -> {
//Your actions when button with name shipment memo is pressed
});
break;
case "Inward Challan":
button.setOnAction(actionEvent -> {
//Your actions when button with name Inward Challan is pressed
});
break;
}
}
}
}
Other Advantage I found with this is, I don't have to show ProgressBar/ProgressIndicator of each scene separately. Since Child Component's ActionEvent is on ParentNode, the ProgressBar/Indicator can be binded to it and works like a charm.
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.
Probably I miss somehting out, but I'm struggeling to find a solution how I can pass dependencies like an instance of my event bus and some service interfaces to my javafx application.
I got an UI-Init class which does not much more than starting the application and receiving some dependencies for the UI like an eventBus:
public class Frontend {
public Frontend(MBassador<EventBase> eventBus) {
Application.launch(AppGui.class);
}
My AppGui class extends Application and loads an FXML:
public class AppGui extends Application {
private Stage primaryStage;
private GridPane rootLayout;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("RootLayout.fxml"));
rootLayout = (GridPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
scene.setFill(null);
primaryStage.setScene(scene);
RootLayoutController rootController = loader.getController();
rootController.init(/*here I would like to inject my eventBus*/);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
Now, how can I pass my eventBus and other service interfaces to this controller? I've read about using DI frameworks like guice (How can JavaFX controllers access other services?) or afterburner.fx to use it. But even if I use guice for the rest of my application, somehow I need to pass the Injector instance to the JavaFX application?.
But Application.launch(AppGui.class); is static and internally creates an AppGui instance on the javafx thread, which I don't get access to. So how I can inject dependencies to my AppGui object without using static variables?
Here is what I do:
The Application class has a couple of lifecycle callbacks, init() and stop().
From the Javadocs:
public void init() throws java.lang.Exception
The application initialization method. This method is called immediately after the Application class is loaded and constructed. An application may override this method to perform initialization prior to the actual starting of the application.
public void stop() throws java.lang.Exception
This method is called when the application should stop, and provides a convenient place to prepare for application exit and destroy resources.
Also from the Javadocs, the lifecycle:
Constructs an instance of the specified Application class
Calls the init() method
Calls the start(javafx.stage.Stage) method
Waits for the application to finish, which happens when either of the following occur:
the application calls Platform.exit()
the last window has been closed and the implicitExit attribute on Platform is true
Calls the stop() method
I start the IoC container in init() and stop it in stop(). Now my Application class has a reference to the IoC container and can supply the first controller with its dependencies.
As a matter of fact, I let the IoC framework manage the controllers. I set them to the loaded FXML using FXMLLoader.setController(), instead of specifying them with fx:controller.
You can pass a static reference to your application class before you call launch(). Something like:
public class Frontend {
public Frontend(MBassador<EventBase> eventBus) {
AppGui.setEventBus(eventBus);
Application.launch(AppGui.class);
}
}
public class AppGui extends Application {
private static MBassador<EventBase> eventBus;
public static void setEventBus(MBassador<EventBase> eventBus) {
this.eventBus = eventBus;
}
private MBassador<EventBase> eventBus;
#Override
public void init() {
if (AppGui.eventBus == null) {
throw new IllegalStateException();
// or however you want to handle that state
} else {
this.eventBus = AppGui.eventBus;
AppGui.eventBus = null;
}
}
}
Whether you keep and use the static reference, or you copy the static reference to a local reference is up to you and the design of your application. If you expect to instantiate more than one copy of AppGui, you may need the local reference.
No idea if this is thread safe (probably not). The advice from #Nikos and #James_D is solid and preferred... but sometimes you just need a hack. :) YMMV
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);
}