fge fge - 1 year ago 109
Java Question

JavaFX Application: is the primary stage in .start() any special?

This is the base fxml file:

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.github.parboiled1.grappa.debugger.basewindow.BaseWindowUi">
<top>
<MenuBar BorderPane.alignment="CENTER">
<Menu mnemonicParsing="false" text="File">
<MenuItem mnemonicParsing="false"
text="New window" onAction="#newWindowEvent"/>
<MenuItem mnemonicParsing="false"
text="Close" onAction="#closeWindowEvent"/>
</Menu>
</MenuBar>
</top>
<center>
<Label text="Hello"/>
</center>
</BorderPane>


The
BaseWindowUi
class is simplistic:

public class BaseWindowUi
{
private BaseWindowPresenter presenter;

public void init(final BaseWindowPresenter presenter)
{
this.presenter = presenter;
}

@FXML
void newWindowEvent(final ActionEvent event)
{
presenter.handleNewWindow();
}

@FXML
public void closeWindowEvent(final ActionEvent event)
{
presenter.handleCloseWindow();
}
}


And so is the presenter:

public final class BaseWindowPresenter
{
private final BaseWindowFactory windowFactory;

public BaseWindowPresenter(final BaseWindowFactory windowFactory)
{
this.windowFactory = windowFactory;
}

public void handleCloseWindow()
{
windowFactory.close(this);
}

public void handleNewWindow()
{
windowFactory.createWindow();
}
}


BaseWindowFactory
is an interface with only one concrete implementation:

// Interface...
public interface BaseWindowFactory
{
void createWindow(Stage stage);

default void createWindow()
{
createWindow(new Stage());
}

void close(BaseWindowPresenter presenter);
}

// Implementation...
public final class DefaultBaseWindowFactory
implements BaseWindowFactory
{
private static final AlertFactory ALERT_FACTORY = new AlertFactory();
private static final URL MAIN_WINDOW_FXML;

static {
try {
MAIN_WINDOW_FXML = DefaultBaseWindowFactory.class.getResource(
"/baseWindow.fxml");
if (MAIN_WINDOW_FXML == null)
throw new IOException("base window fxml not found");
} catch (IOException e) {
ALERT_FACTORY.showError("Fatal error", "cannot load base FXML", e);
throw new ExceptionInInitializerError(e);
}
}

private final Map<BaseWindowPresenter, Stage> windows
= new HashMap<>();

private final AtomicInteger windowCount = new AtomicInteger();

@Override
public void createWindow(final Stage stage)
{
final FXMLLoader loader = new FXMLLoader(MAIN_WINDOW_FXML);
final Pane pane;
try {
pane = loader.load();
} catch (IOException e) {
ALERT_FACTORY.showError("Fatal error", "unable to create window",
e);
return;
}

final BaseWindowPresenter presenter = new BaseWindowPresenter(this);

final BaseWindowUi ui = loader.getController();
ui.init(presenter);

stage.setScene(new Scene(pane));
stage.setTitle("window " + windowCount.getAndIncrement());

windows.put(presenter, stage);

stage.show();
}

@Override
public void close(final BaseWindowPresenter presenter)
{
windows.get(presenter).close();
}
}


Finally, here's the class implementing
Application
:

public final class TestApp
extends Application
{
private final BaseWindowFactory factory = new DefaultBaseWindowFactory();

@Override
public void start(final Stage primaryStage)
throws Exception
{
factory.createWindow(primaryStage);
}

public static void main(final String... args)
{
launch(args);
}
}


All of these work; I can open new windows, close the one created from the "primary stage", the application exits correctly when all windows are closed etc.

So, then, how is the primary stage special?

What is more, the documentation for
Application
says this:


JavaFX creates an application thread for running the application start method, processing input events, and running animation timelines. Creation of JavaFX Scene and Stage objects as well as modification of scene graph operations to live objects (those objects already attached to a scene) must be done on the JavaFX application thread.


My code works currently but it does pretty much nothing at all; won't I eventually run into the problem of code which should run in the application thread but does not? are all
new Stage()
s automatically aware of the application thread?

Answer Source

Primary Stage

The only thing special about the primaryStage is that (unlike any other stage), it is created by the JavaFX system and not by your application. But other than that, the primary stage behaves like other stages.

There is a stage related rule relating to application lifecycle:

  • Waits for the application to finish, which happens when either of the following occur:
    • the application calls Platform.exit() OR
    • the last window has been closed and the implicitExit attribute on Platform is true

So, if you never show the primaryStage (or any other stage) in your application and you have the default implicitExit setting, your application will never exit, so always be sure to invoke primaryStage.show().

Perhaps the primary stage seems special to you because it happens to be the last window you are closing, so the application is automatically exiting after it is shut. But if some other window was the last one closed, the application would exit just the same, so the primary stage isn't really special in that regard.

Threading

There is really only one rule related to threading, and you have already quoted it in your question:

  • Creation of JavaFX Scene and Stage objects as well as modification of scene graph operations to live objects (those objects already attached to a scene) must be done on the JavaFX application thread.

The code you provided does not introduce any new threads, it just uses the thread which is passed to it by the JavaFX system (the JavaFX application thread). So your code as provided could never violate the above rule (as pointed out by Vitomir in his comment).

Your DefaultBaseWindowFactory createWindow method should only ever be run on the JavaFX application thread, so you don't need concurrency utilities such as AtomicInteger to encapsulate the number of windows, a simple integer would suffice as the only thread which reads or writes that value will be operating on the JavaFX application thread.

Should you ever introduce new code which creates new threads (either code you write or code from a library), then take care that this code does not modify objects in the active scene graph and does not try to create windows directly from another thread (if in doubt, you can easily check what thread you are executing on by System.out.println(Thread.getName()). If you do end up having some multi-threaded code, use Platform.runLater() to wrap any calls to functions which manipulate nodes in the SceneGraph or create new windows, or use the JavaFX concurrency utilities to manage the concurrent services.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download