JavaFX Object is always null after launch - java

I'm trying to create an Application, using JavaFX. But the JavaFX Application is not the Main entrance of the Application.
I'm using a Main-Class, a Controller-Class (which controlls everything), and other classes like the JavaFX Application
Main -(calls)-> Controller -(creates)-> JavaFX Application
After the JavaFX Application Object is created, the Controller calls a method, so the JavaFX Application Object has an instance of Controller
But this object is always null, as soon as I'm outside of the method-call.
Main
public class Main{
public static void main(String[] args){
Controller c = new Controller();
}
}
Controller
public class Controller{
private MyApplication app;
public Controller(){
app = new MyApplication(); //create Application
app.setController(this); //set Controller Object
app.startApplication(); //launch the application
}
}
MyApplication
public class MyApplication extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private Controller controller;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
initRootLayout();
}
public void setController(Controller con){
this.controller = con;
}
public void startApplication(String... args){
launch(args);
}
public void initRootLayout(){
System.out.println(controller==null); //returns true. But why?
try{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MyApplication.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
}
catch(IOException e){
e.printStackTrace();
}
}
}
How come, the Controller Instance within MyApplication is always null. The only time it's not null is within the call setController

Since JavaFX doesn't have system tray access, you basically need an AWT application to run in the system tray. I would then consider embedding the JavaFX aspects in Swing using a JFXPanel, and basically make it a Swing/AWT application with some JavaFX embedded.
Alternatively, you could launch everything from an Application subclass, and just bootstrap the AWT part in the start method, setting up the triggers to do the JavaFX stuff when needed. That feels uglier though.
Finally, your approach only really fails because you need to pass an object to the JavaFX application. If it makes sense to make that object a singleton, then you could just let the JavaFX application retrieve it, rather than passing the object to it.

I think your problem is that you are referencing different instances of your Controller class. After the call to launch(args) JavaFX creates its own private instance of your Application class. When you call setController you are setting the controller for a different instance of your class than what JavaFX is using. One way to fix this would be to make the controller variable and its setter static.

Related

ScalaFX (or JavaFX) - Calling GUI from another main class multiple times

I am following on from a previous question: link
I am writing a standalone visualization package for a simulation package
The simulation package is written by one of our team in Scala
What I want to do, is create a matplotlib like package for this simulation package. My envisioned end use would look something like matplotlib:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()
plt.plot([10, 20, 3000, 4121212])
plt.ylabel('some numbers')
plt.show()
For my package, I would do something analogous. Assume here that myFXPackage is a chart package written in ScalaFX. In my DRIVER class:
Import myFXPackage
// run some simulation code here...
myFXPackage.plot(results)
// run some MORE simulation code here...
myFXPackage.plot(results)
Now it seems that for ScalaFX, there can only be one entry point for the whole app; this is the JFXApp class. However, I want to import the package and simply run this code multiple times in my DRIVER class as shown above. So somehow how DRIVER class would call ScalaFX and run the plot, close it, then run another plot.
Is this feasible? If so how would I go about doing this?
Most JavaFX example code conflates the main method with the Application subclass, and in many cases even does the UI layout, etc., in the same class. There's not necessarily a reason to do this, and in the latter case it's not a particularly good design.
Assuming you separate concerns appropriately in your UI code, you might have a class like
public class MainUI {
private BorderPane root ;
public MainUI() {
root = new BorderPane();
// do layout, register event handlers, etc etc
}
public Pane getView() {
return root ;
}
}
Then if you want a standalone JavaFX application, you would do
public class JavaFxApp extends Application {
#Override
public void start(Stage stage) {
MainUI ui = new MainUI();
Scene scene = new Scene(ui.getView());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
But this isn't the only place you can use the UI class. In JavaFX (as in most UI toolkits), you must create a new stage, and perform other UI tasks, on the UI thread (i.e. the FX Application Thread).
Additionally, in JavaFX you must explicitly start up the FX toolkit. In the code above, this is done by the Application.launch() method.
JavaFX 9 introduced a Platform.startup(Runnable), which starts the FX toolkit and executes the supplied Runnable on the FX Application Thread.
So, using JavaFX 9 and later, you can have code like
public class SomeApp {
public static void main(String[] args) {
// start FX toolkit
// Since we don't want it to exit when we close a window, set
// implicit exit to false:
Platform.startup(() -> Platform.setImplicitExit(false));
// do some other stuff...
// whenever you need to:
Platform.runLater(SomeApp::showStage);
// do other stuff...
}
private static void showStage() {
Scene scene = new Scene(new MainUI().getView());
Stage stage = new Stage();
stage.show();
}
}
Prior to JavaFX 9, you can still do this, but you need to launch a "blank" application:
public class FXStartup extends Application {
#Override
public void start(Stage ignored) {
// probably need this:
Platform.setImplicitExit(false);
}
}
Note that the launch() method blocks until the toolkit exits, so your application needs to start it in another thread:
public class SomeApp {
public static void main(String[] args) {
// start FX toolkit
new Thread(() -> Application.launch(FXStartup.class, args)).start();
// do some other stuff...
// whenever you need to:
Platform.runLater(SomeApp::showStage);
// do other stuff...
}
private static void showStage() {
Scene scene = new Scene(new MainUI().getView());
Stage stage = new Stage();
stage.show();
}
}

Java Fx Splash screen with spring boot

JavaFX 11 and Spring Boot 2.0.
I want to display a splash screen until Spring inits all of its necessary beans, and in the spring.run() I want to close the splash stage(or at least after x amount of seconds). Such that connecting to the DB creating POJOs etc. But when I try to show my splash screen before FX thread kicks in so it throws:
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main()
I even tried in the Platform.runLater() but still did not work out. Is there any work around for this problem? Thanks.
public class StartUp extends Application{
public static void main(String[] args) {
loadSplashScreen();
appContext = SpringApplication.run(StartUp.class);
launch(args);
}
#Override
public void start(Stage stage) {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
stage.setScene(new Scene(root, 300, 275));
stage.show();
}
static void loadSplashScreen() {
Stage splashStage = new Stage();
try {
BorderPane splashPane = FXMLLoader.load(getClass().getResource("splash.fxml"));
Scene splashScene = new Scene(splashPane);
splashStage.setScene(splashScene);
splashStage.show();
setFadeInOut(splashPane, splashStage);
} catch (IOException e) {
e.printStackTrace();
}
}
static void setFadeInOut(Parent splashScene, Stage splashStage) {
FadeTransition fadeIn = new FadeTransition(Duration.seconds(3), splashScene);
fadeIn.setFromValue(0);
fadeIn.setToValue(1);
fadeIn.setCycleCount(1);
FadeTransition fadeOut = new FadeTransition(Duration.seconds(3), splashScene);
fadeOut.setFromValue(1);
fadeOut.setToValue(0);
fadeOut.setCycleCount(1);
fadeIn.play();
fadeIn.setOnFinished((e) -> fadeOut.play());
fadeOut.setOnFinished((e) -> splashStage.close());
}
}
In your code you have a method called loadSplashScreen() which you call before the Application.launch(). It will be the call to the launch method that starts the JavaFX thread which is why your loadSplashScreen() method fails i.e. the JavaFX thread hasn't even started when this method is called.
You might want to take a look here at this Oracle tutorial on PreLoaders to understand how to understand a basic example before you try work with starting JavaFX with Spring Boot.
Although I haven't booted JavaFX from Spring Boot, I have done similar in an OSGi bundle and you might like to take a look at my FlexFx GitHub repo here which might give you a few pointers on how to use a pre-loader with Spring Boot but note I currently do not have the ability to display a splash screen in my project.
Finally, your issue would happen on JavaFX-8, 9 or 10. It's not specific to JavaFX-11.

Initializing second window in separate class (e.g. ChoiceBox)

I have two window controllers, both of which load an FXML file and show a screen. The first, GUIController is the main window, which spawns a second window, PackageBuilder, inside which the user inputs some data and it's stored.
The second window is spawned via a Button in the main window like so (FXML linked):
#FXML private void onNewPackage(ActionEvent e){
PackageBuilder pb = new PackageBuilder(owner); // Scene owner
}
I figured the second class cannot call the JavaFX Application launch method and the original Scene needs to be passed along. So I wrote this inside PackageBuilder:
public PackageBuilder(Scene owner) {
/* Removed redundant setup info */
fstart(owner);
}
private void fstart(owner) {
Stage window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader;
Scene scene;
try {
loader = new FXMLLoader(getClass().getResource("PackageBuilder.fxml"));
scene = new Scene(loader.load());
window.initOwner(owner);
window.setScene(scene);
window.showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
This all would be all good, if the initialize-method could be called. But it can't. So is there a way to for example initialize the elements of a ChoiceBox anywhere else?
I'm assuming there's a stupid mistake somewhere. There always is.
The second class with a ChoiceBox to be initialized can implement an interface:
public class PackageBuilder implements Initializable{
#Override
public void initialize(URL url, ResourceBundle rb){
/* Initialize things needed */
}
}
The method is called before the constructor of the class.

JavaFX - Why does JavaFx create a primary stage instead of allowing me to do it?

I would like to create a class called EnhancedStage which adds functionality to Stage.
The problem is, javaFx creates the primary stage and passes it into the application, instead
of allowing me to create it. This way I'm not able to use my own class for the primary stage.
I don't understand why the system creates the Stage, and would I be losing anything by not
using that stage and instead constructing another one ?
Thanks
If you don't want to use the stage which is a parameter of the start() method, then don't call show() on that stage, just create your own stage and only call show() on your custom stage. I suggest doing this inside the start method rather than via a runLater call from init. I don't see any significant drawback in such an approach.
As to why a stage is passed in start, MadProgrammer's guess is as good as any: Simplifies life for programmers, removes some boilerplate code from basic apps and limits app startup coding errors. For all those reasons, for most people, I'd generally just advise using the provided stage rather than creating your own.
Suggested approach if you need or desire to use a stage subclass for your primary stage:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class StageTester extends Application {
#Override public void start(final Stage systemStage) throws Exception {
UserStage userStage = new UserStage();
userStage.show();
}
private class UserStage extends Stage {
FlowPane layout = new FlowPane();
UserStage() {
super();
foo();
setScene(new Scene(layout));
}
public void foo() {
layout.getChildren().add(new Label("foo"));
}
}
public static void main(String[] args) { launch(args); }
}
There is nothing stopping you from creating another stage and ignoring the one you get from the start method. There are number of ways to do this:
Extend Stage and add your additional functionality
public class EStage extends javafx.stage.Stage {
... // Do your magic here
}
or Extend Window
public class EStage extends javafx.stage.Window {
}
In either case, to use your new stage, you can just create it in the init method and use it in the start method, ignoring the stage supplied to you
public class ShowCase extends Application {
private Stage mStage;
#Override
public void init() throws Exception {
Platform.runLater(() -> {
mStage = new EStage();
//mStage.impl_setPrimary(true);
}
}
#Override
public void start(Stage primaryStage) {
mStage.setScene(...)
}
}
The JavaFX architecture is based upon a container which manages the JavaFX Event Loop (on the JavaFX application thread) for your application (which is why the entry class for your application must inherit from javafx.application.Application).
Because JavaFX applications are container managed, you gain a few benefits:
the container can inject JavaFX control references into your controller class instances via the FXML loader
the container manages event propagation from the event loop all the way down to the target control and back up to the event loop again without complex event handling on your part
Of course, the event loop requires a top-level object for event propagation and this top-level object is the Stage object, which gets passed to the start() method of your Application object.

NullPointerException when using TextArea.append()

I realize there is an nearly identical question in title. The question does not seem to be relevant to my particular issues.
I'm using the JavaFX scene builder to create my UI, (which includes the TextArea in question). All I want to do is take the message I get from the server and post it into the text area. Through various println statements I have narrowed the problem to this. I have tried various solutions (for hours); coming here was a last resort.
The only other possible cause I can think of would be something going wrong with the multithreading, but can even begin to think of what.
public class IncomingReader implements Runnable
{
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
FXMLDocumentController.controller.chatBox
.appendText(message + "\n"); /////////PROBLEM HERE//////
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
FXML controller class (relevant line only):
#FXML protected TextArea chatBox;
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader());
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The line of FXML that defines the chatBox:
<TextArea id="ChatBox" fx:id="chatBox" focusTraversable="false" mouseTransparent="true" prefHeight="400.0" prefWidth="200.0" promptText="Chat:" wrapText="true" BorderPane.alignment="CENTER">
The resulting exception:
Exception in thread "Thread-4" java.lang.NullPointerException
at javachat.IncomingReader.run(IncomingReader.java:28)
at java.lang.Thread.run(Thread.java:745)
Note: this is really covered fully in Passing Parameters JavaFX FXML. However, the code you posted is so far from being structured properly to use the approaches there, that you probably need to see it specifically for your example. I would strongly recommend you read through that post and understand it, though.
Why you are seeing a NullPointerException:
FXMLDocumentController.controller refers to an instance of FXMLDocumentController which is not the same instance that was created for you by the FXMLLoader. Consequently, the chatBox field in FXMLDocumentController.controller was not initialized by the FXMLLoader.
What FXMLLoader does:
When you call one of the FXMLLoader.load(...) methods, it basically does the following:
Parses the FXML file (or stream)
If the root element of the FXML contains an fx:controller attribute, and no controller has been set by other means, it creates an instance of the class specified in that attribute
Creates the object hierarchy described by the FXML. If any of the objects defined in FXML have fx:id attributes, and a controller is associated with the FXMLLoader, it initializes #FXML annotated fields of the controller with the corresponding objects
Associates event handlers with the nodes, where defined
Returns the root object of the FXML object hierarchy
How to access the controller after loading the FXML
To access the controller, you must create an FXMLLoader instance instead of relying on the (evil) static FXMLLoader.load(URL) method. You can either pass the URL of the FXML resource into the FXMLLoader constructor, or call setLocation(...) on the FXMLLoader. Then, to load the FXML file, just call the no-argument load() method on the FXMLLoader. Once that is complete, you can access the controller by calling getController() on the FXMLLoader instance.
Other issues in your code
You cannot update the UI from a background thread: you must update it from the JavaFX Application Thread. As it stands (if you fix your NullPointerException issue), your code would throw an IllegalArgumentException in Java 8. In JavaFX 2.2 and earlier you would have to live with the possibility of bugs showing up at arbitrary times in the future. You can schedule code to be executed on the FX Application Thread by wrapping that code in a call to Platform.runLater().
Not quite so bad, but imo a bad design, is that you are exposing UI elements (the TextArea) outside of your FXML-controller pair. This becomes a real issue when your boss comes into your office and tells you he doesn't want the messages displayed in a TextArea any more, he wants them in a ListView. Since the TextArea is exposed, you have to trawl through all your code looking for any references to it. A better approach is to define a appendMessage(String) method in your controller, and access it. You might even want to factor all the data out into a separate model class, and pass references an instance of that class to both the controller and the reader class, but that is beyond the scope of this question.
I will refrain from complaining about your overuse of static stuff.. ;).
Here's the skeleton of one way to fix this code:
public class IncomingReader implements Runnable
{
private final FXMLDocumentController controller ;
public IncomingReader(FXMLDocumentController controller) {
this.controller = controller ;
}
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
final String msg = message ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.appendMessage(msg + "\n");
}
});
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
Controller:
public class FXMLDocumentController {
#FXML
private TextArea chatBox ;
public void appendMessage(String message) {
chatBox.appendText(message);
}
// other stuff as before...
}
Application:
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
FMXLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = (FXMLDocumentController) loader.getController();
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader(controller));
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Categories