javascalajavafxjvmscalafx

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


I am following on from a previous question: link

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?


Solution

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