JavaFX 应用程序:.start() 中的初级阶段有什么特别之处吗?

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

这是基础 fxml 文件:

<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>

BaseWindowUiclass简单化:

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

主持人也是:

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是一个只有一个具体实现的接口:

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

最后,class 实现 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);
    }
}

所有这些工作;我可以打开新的 windows,关闭从 "primary stage" 创建的那个,应用程序在所有 windows 关闭时正确退出等

那么,那么,初级阶段有什么特别之处呢?

此外,Application 的文档是这样说的:

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.

我的代码目前可以工作,但几乎什么也没做;我最终不会 运行 陷入应用程序线程中应该 运行 但没有的代码问题吗?所有 new Stage() 都自动识别应用程序线程吗?

初级阶段

primaryStage 的唯一特别之处在于(与任何其他阶段不同),它是由 JavaFX 系统而不是您的应用程序创建的。但除此之外,初级阶段的行为与其他阶段一样。

有一个关乎application lifecycle的阶段相关规则:

  • 等待应用程序完成,这在发生以下任一情况时发生:
    • 应用程序调用 Platform.exit()
    • 最后一个 window 已关闭并且 Platform 上的 implicitExit 属性为真

因此,如果您从不在您的应用程序中显示 primaryStage(或任何其他阶段)并且您有默认的 implicitExit 设置,您的应用程序将永远不会退出,因此请始终确保调用 primaryStage.show()。

也许初级阶段对您来说似乎很特别,因为它恰好是您关闭的最后一个window,所以应用程序在关闭后会自动退出。但是如果其他 window 是最后一个关闭的,应用程序将同样退出,所以初级阶段在这方面并不是很特别。

线程

关于线程的规则真的只有一个,你已经在你的问题中引用了它:

  • 必须在 JavaFX 应用程序线程上创建 JavaFX Scene 和 Stage 对象以及将场景图操作修改为活动对象(那些已附加到场景的对象)。

您提供的代码没有引入任何新线程,它只是使用JavaFX系统传递给它的线程(JavaFX应用程序线程)。因此,您提供的代码永远不会违反上述规则(正如 Vitomir 在他的评论中指出的那样)。

您的 DefaultBaseWindowFactory createWindow 方法只能在 JavaFX 应用程序线程上使用 运行,因此您不需要 AtomicInteger 等并发实用程序来封装 windows 的数量,一个简单的整数会足以读取或写入该值的唯一线程将在 JavaFX 应用程序线程上运行。

如果您引入创建新线程的新代码(您编写的代码或库中的代码),请注意此代码不会修改活动场景图中的对象并且不会尝试创建 windows 直接从另一个线程(如果有疑问,您可以通过 System.out.println(Thread.getName() 轻松检查您正在执行的线程)。如果您最终拥有一些多线程代码,请使用 Platform.runLater() to wrap any calls to functions which manipulate nodes in the SceneGraph or create new windows, or use the JavaFX concurrency utilities 来管理并发服务。

实际上有一个(重要但不明显的)区别:用户可以通过单击 window 装饰中的最小化按钮来 最小化 初级阶段。对于其他阶段,window 最小化按钮被禁用。我们必须以不同的方式设计我们的应用程序来解决这个问题。

使用 Java 1.8.0_121.

测试