Issue
I'm creating a simple javaFX program. In HelloApplication
I want to do the logic, and in HelloController
I want to listen to button clicks which then execute methods inside the HelloApplcation
.
HelloApplication.java:
Timeline timeline;
@FXML
HelloController HC;
FXMLLoader fxmlLoader;
private int counter=0;
public void start(Stage stage) throws IOException {
fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 640, 480);
stage.setTitle("Program!");
stage.setScene(scene);
stage.show();
StartCounter("start");
}
public void StartCounter(String status)
{
HC = fxmlLoader.getController();
if(timeline==null) {
timeline = new Timeline(new KeyFrame(Duration.seconds(1), ev -> {
HC.getCmd().setText(String.valueOf(counter));
counter++;
}));
timeline.setCycleCount(Animation.INDEFINITE);
}else{
if(status=="stop")
timeline.stop();
else if(status=="start")
timeline.play();
}
}
HelloController.java:
HelloApplication helloApp=new HelloApplication(); //this creates another instance...
@FXML
protected void onHelloButtonClick() {
helloApp.StartCounter("stop");
}
But how can I access HelloApplication object that was created on launch()
?
Something like loader.getApplication()
would be perfect instead of creating a new object in controller (which doesn't work anyway).
Right now, after mouse click the helloApp.StartCounter() causes: java.lang.reflect.InvocationTargetException
Solution
How can I access the Application object in Controller?
To directly answer your question.
- Save the application instance to a static variable in the application
init
method. - Provide a static accessor method to access it.
This is kind of like a singleton pattern, but the application instance is created by the launch
method.
public class AccessibleApp extends Application {
private static AccessibleApp applicationInstance;
public static AccessibleApp getApplicationInstance() {
return applicationInstance;
}
@Override
public void init() {
applicationInstance = this;
}
// other app functionality ...
public static void main(String[] args) {
launch(args);
}
}
Then the application can be accessed from any class using:
AccessibleApp.getApplicationInstance()
Issues with your sample code and approach
You should not create another application instance.
The application lifecycle javadoc notes there should be only one application instance in the JVM and this will be created by the invocation of the
launch()
method.The
launch()
method can only be called once for the JVM process.There should never be an
@FXML
annotation in an Application class.@FXML
is only used in a controller and the application class should not also be a controller.Your overall approach is incorrect:
in HelloController I want to listen to button clicks which then execute methods inside the HelloApplcation
You should not do this.
The application class is responsible for the application lifecycle. Unless it is a trivial self-contained hello world style application, it should not be doing anything else. It definitely should not be servicing fxml controller callbacks.
Recommended Approach
Perform your application logic either directly in the controller or in a dedicated service class.
- Write a Counter class that has an integer property for the counter value and a timeline.
- Provide start and stop methods on the class, do not use a string status for that.
- In the controller create a Counter instance and start and stop it on user interaction.
- In the controller initialize method, bind the display field text to the counter value.
Rather than trying to debug and fix your current code, I recommend you work on implementing the suggested approach instead.
To understand such approaches more, see:
Answered By - jewelsea
Answer Checked By - Candace Johnson (JavaFixing Volunteer)