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

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();
}
}

Related

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.

JavaFX Update Issue?

I'm working on a game in JavaFX, and right now I'm trying to create a loading screen, since loading the assets takes some time. I've created a LoadingPane class that displays several progress bars, and I know for sure that it works. However, in the below code, the loading pane will not be visible until after the loadAssets function, even though I'm adding it beforehand. When I run the below code, I get a blank stage for the time it takes for the assets to load, and then a screen with the completed progress bars.
I haven't been able to find anyone with similar issues, or any sort of refresh or update function to force the scene to display the loading pane before continuing with the program.
Note: I've deleted some irrelevant code setting up keyboard input handling.
public class Main extends Application{
Pane root = new Pane();
Scene mainScene = new Scene(root, Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT, Color.BLACK);
static LoadingPane loadingPane = new LoadingPane(3);
private static int loadingIndex = 0;
public static void main(String[] args) {
if(Constants.DEBUG_MODE)
System.out.println("WARNING: Game has launched in debug mode!");
launch(args);
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
public static void loadAssets(){
RoomLoader.createRooms();
updateProgress(1.0);
loadingIndex++;
ProjectileLoader.load("imgs/projectiles/");
ProjectileLoader.load(Constants.BATTLE_IMAGES_FILEPATH);
updateProgress(1.0);
loadingIndex++;
BattleLoader.createBattles();
updateProgress(1.0);
loadingIndex++;
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
#Override
public void start(final Stage primaryStage) {
//root.getChildren().add(new javafx.scene.image.ImageView(new Image("imgs/loading.png")));
root.setLayoutX(0);
primaryStage.setScene(mainScene);
primaryStage.show();
primaryStage.toFront();
primaryStage.setTitle("Branch");
primaryStage.setResizable(false);
//primaryStage.getIcons().add(new Image("core/imgs/soul/red.png"));
//This allows the closing of the primaryStage to end the program
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>(){
#Override
public void handle(WindowEvent t) {
System.exit(0);
}
});
root.resize(Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT);
primaryStage.getIcons().add(new Image("imgs/icon.png"));
//End GUI setup
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
}
}
EDIT: Working Code
For anyone who arrives here with a similar issue, below is the code I used to get this to work:
I replaced this:
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
With this:
root.getChildren().add (loadingPane);
Task<Integer> loadingTask = new Task<Integer>() {
#Override protected Integer call() throws Exception {
loadAssets();
return 1;
}
};
loadingTask.setOnSucceeded(new EventHandler<WorkerStateEvent>(){
#Override
public void handle(WorkerStateEvent t){
loadingPane.setVisible(false);
load(); //note: this function sets up the actual game
//updating the GUI, adding game elements, etc
}
});
new Thread(loadingTask).start();
I can't say that this is the best way to go about this, but I can say that it works. Good luck!
You need a separate thread for the update method.
Code runs in a linear fashion, one bit of code runs then the next. With a separate thread, the two “lines” of code can run side by side. The process runs and the GUI updates at the same time.
JavaFX application runs on specific thread called Application Thread, in other to make GUI responsive while doing some expensive operation, like in your case loading assets, you will need to load assets on another Thread that you create yourself or you can use Task which is one of JavaFX classes meant to be used in such use cases.
I suggest you to read about Task in official javadocs

JavaFX software design

In a JavaFX application, javafx.application.Application must be subclassed, and the inherited launch() method, although it's public, must be called from within this derived class, otherwise an exception is thrown. The launch() method then uses reflection to instantiate the derived class, making it difficult to set values for the class members without losing them when launching. All that appears totally unusual to me, and I was wondering why starting a JavaFX application is so complicated, if that kind of software design (design pattern?) has a name, or if it's just bad design?
EDIT:
To be more specific, I want to use the observer pattern, so my java application gets notified when a document was loaded, like this:
public class MyDocumentLoader extends Application
{
private ChangeListener<Worker.State> changeListener;
public void setChangeListener(ChangeListener<Worker.State> changeListener)
{
this.changeListener = changeListener;
}
...
public void loadDocument(String url)
{
webEngine.getLoadWorker().stateProperty().addListener(changeListener);
webEngine.load(url);
}
...
}
I need the callback member in several methods, and ideally I can have more than one instances of the class that loads documents, so I can set different ChangeListeners for different URLs.
My guess is that this design was motivated by the (vast) number of Swing applications that were incorrectly written, with the "primary" JFrames being instantiated and shown on the wrong thread (i.e. not on the AWT event dispatch thread). My guess is that so many Swing applications were incorrectly written that they had to defensively code the framework against the incorrect usage, and that they wanted to avoid this scenario with JavaFX.
Forcing (well, almost forcing, there are hack-arounds) an FX Application to start this way makes it much harder to write an application incorrectly in a similar way. The launch method (and the equivalent Oracle JVM startup process if you have an Application subclass without a main method and a call to launch) does quite a bit of boilerplate work: it starts the FX toolkit, instantiates the Application subclass and calls its init() method, then on the FX Application Thread it instantiates the primary Stage and passes it to the Application subclass's start(...) method. This then ensures everything is running on the correct thread.
You should basically consider the start(...) method in a JavaFX application as the replacement for the main(...) method in a "traditional" Java application, with the understanding it is invoked on the FX Application Thread.
My recommendation is that the Application subclass should be as minimal as possible; it should just delegate to something else to actually create the UI, and then should just place it in the primary stage and show it. Include a main method that does nothing other than call launch(...) as a fallback for non-JavaFX-aware JVMs. You should only have one instance of one Application subclass present in any JVM. This way your Application subclass has no class members to set, and so the issues you describe simply don't arise.
If you use FXML, this is actually fairly natural: the start(...) method essentially just delegates to the FXML-controller pair to do the real work. If you don't use FXML, create a separate class to do the actual layout, etc, and delegate to it. See this related question which gets at the same kind of idea.
Note also that your statement
the inherited launch() method, although it's public, must be called
from within this derived class
is not entirely accurate, as there is an overloaded form of the launch(...) method in which you can specify the application subclass. So, if you really need, you can just create a stub for starting the FX toolkit:
public class FXStarter extends Application {
#Override
public void start(Stage primaryStage) {
// no-op
}
}
Now you can do:
public class MyRegularApplication {
public static void main(String[] args) {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
// other stuff here...
}
}
Note that launch does not return until the FX toolkit shuts down, so it is imperative to put this call in another thread. This potentially creates race conditions, where you may try to do something needing the FX toolkit before launch(...) has actually initialized it, so you should probably guard against that:
public class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void init() {
latch.countDown();
}
#Override
public void start(Stage primaryStage) {
// no-op
}
}
and then
public class MyRegularApplication {
public static void main(String[] args) throws InterruptedException {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
// other stuff here...
}
}
SSCCE (I just used inner classes for everything so this is convenient to run, but in real life these would be standalone classes):
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BackgroundProcessDrivenApp {
public static void main(String[] args) throws InterruptedException {
Platform.setImplicitExit(false);
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
new MockProcessor().doStuff() ;
}
public static class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
#Override
public void init() {
latch.countDown();
}
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void start(Stage primaryStage) { }
}
public static class MockProcessor {
private final int numEvents = 10 ;
public void doStuff() {
Random rng = new Random();
try {
for (int event = 1 ; event <= numEvents; event++) {
// just sleep to mimic waiting for background service...
Thread.sleep(rng.nextInt(5000) + 5000);
String message = "Event " + event + " occurred" ;
Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
}
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} finally {
Platform.setImplicitExit(true);
}
}
}
public static class Messager {
private final String message ;
public Messager(String message) {
this.message = message ;
}
public void showMessageInNewWindow() {
Stage stage = new Stage();
Label label = new Label(message);
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
VBox root = new VBox(10, label, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 120);
stage.setScene(scene);
stage.setAlwaysOnTop(true);
stage.show();
}
}
}
JavaFX supports a great number of deployment and packaging strategies, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html, and having a standardized lifecycle entry- and exit-point simplifies supporting all these strategies.
If you are struggling to initialize your main application class, due to it being instanciated by the JavaFX launcher, your best option is to use the Application.init() and Application.stop() methods, as James_D points out.

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.

JavaFX Object is always null after launch

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.

Categories