I\'m trying to run a program using JavaFX. If I was using Swing, I would have one class started by the main method, and have it build the GUI class. That would give me 2 threads
It's not really different from Swing, both FX and Swing use their own Event dispatch thread.
Take this example that runs 3 applications in one:
import java.util.Scanner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TripleFun {
public static class CmdApplication {
public void doThings() {
Scanner in = new Scanner(System.in);
while (true) {
String line = in.nextLine();
if ("quit".equals(line))
return;
println("Hello: " + line);
}
}
}
public static class SwingApplication extends JFrame {
public SwingApplication() {
setTitle("Swing");
setSize(200, 200);
setVisible(true);
JButton button = new JButton("Ok.");
button.addActionListener(e -> println("Swing button"));
add(button);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
println("Swing Thread");
}
}
public static class FxApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
println("Fx Thread");
StackPane root = new StackPane();
Button button = new Button();
button.setText("Ok.");
button.setOnAction(e -> println("Fx button"));
root.getChildren().add(button);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("FX");
primaryStage.setScene(scene);
primaryStage.show();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new SwingApplication();
println("swing invoke done.");
});
println("Started Swing.");
new Thread(() -> {
println("FX launch thread started.");
Application.launch(FxApplication.class, args);
println("FX launch thread done.");
}).start();
println("Started Fx.");
new Thread(() -> {
println("Cmd thread started.");
new CmdApplication().doThings();
println("Cmd thread done.");
}).start();
println("Started cmd. initial thread done.");
}
public static void println(String msg) {
System.out.println(Thread.currentThread().getName() + " - " + msg);
}
}
with some output like
main - Started Swing.
main - Started Fx.
Thread-1 - FX launch thread started.
main - Started cmd. initial thread done.
Thread-2 - Cmd thread started.
AWT-EventQueue-0 - Swing Thread
AWT-EventQueue-0 - swing invoke done.
JavaFX Application Thread - Fx Thread
AWT-EventQueue-0 - Swing button
AWT-EventQueue-0 - Swing button
JavaFX Application Thread - Fx button
JavaFX Application Thread - Fx button
ok
Thread-2 - Hello: ok
ok
Thread-2 - Hello: ok
quit
Thread-2 - Cmd thread done.
Thread-1 - FX launch thread done.
The initial thread that has the main
method ends almost immediately, each of the sub - applications continue running on their own thread. The application as a whole ends once all threads are done.
The only tricky bit about JavaFX is that it's launch
method blocks until the app (which runs in it's own thread) is done. You can either let it take over the initial main
thread and run your other code in a new thread, or move the launch to a new thread to keep the main one free.
In JavaFX, I'm guessing in order to prevent the very common threading errors that were present in many Swing applications, the startup process is constrained so that (more or less) the only way to do things forces you to execute the UI code on the FX Application Thread:
public class MyAppStartClass extends Application {
@Override
public void start(Stage primaryStage) {
// this method will be executed on the FX Application Thread
// load UI and display it here....
}
}
In an Oracle JRE, executing java MyAppStartClass
will (in contrast to a regular Java application), cause an instance of MyAppStartClass
to be created, the FX Application Thread to be started, and the start
method of the created instance to be executed on the FX Application Thread. (There is a bit more to it than that, but that's the basic gist.)
If you want to support environments that aren't aware of how to execute a JavaFX Application (including many IDEs), you can add a main
method that forces this to happen, simply by calling the static Application.launch()
method:
public class MyAppStartClass extends Application {
@Override
public void start(Stage primaryStage) {
// this method will be executed on the FX Application Thread
// load UI and display it here....
}
// to support non-JavaFX-aware environments:
public static void main(String[] args) {
launch(args);
}
}
Note there's an overloaded form of launch
, where you can specify an Application
subclass, so you could have a different main class (the use cases for this are very few):
public class MainClass {
public static void main(String[] args) {
Application.launch(MyAppStartClass.class, args);
}
}
There are two important features of the launch
method to be aware of:
If you want to have a thread doing work besides the FX Application Thread, the simplest way is to start it directly from the start method:
public class MyAppStartClass extends Application {
@Override
public void start(Stage primaryStage) {
// start a background thread to do background stuff:
new Thread(() -> {
// background work...
}).start();
// UI work...
}
}
If you compare this to the standard startup for a Swing application:
public class MySwingApp {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// UI work...
});
// background work...
}
}
the process is kind of inverted compared to Swing. In Swing you (are supposed to) explicitly have the startup method (main) specify that the UI is run on the AWT event dispatch thread, and you can do other stuff in the "current" thread in which main is executed. In JavaFX the startup method (start) is executed on the UI thread, and if you want to do stuff on another thread you explicitly launch one.
Other rules are pretty much the same: UI elements that are part of the scene graph can only be modified on the UI thread, etc. Note JavaFX has a specific concurrency API for managing tasks in a background thread and scheduling UI updates on the FX Application Thread.
Aside:
In theory I suppose you could do something like:
public class MainClass {
public static void main(String[] args) {
new Thread(() -> Application.launch(MyAppStartClass.class, args)).start();
// do "background thread" work here in the (now free) main thread
}
}
But since that idiom is far from the usual setup, I would not recommend it. Note especially you should not have a main method like this directly in the Application
subclass, as (I think) an Oracle JRE (or an environment that knows how to run JavaFX Applications) might ignore the main method and just boot up your start method as described at the top of this post.