Leprechaun Leprechaun - 4 months ago 21
Java Question

How to define Stage in FXML?

I want to define my GUI entirely in FXML. I started with JavaFX templates that are shown everywhere from Oracle docs to Netbeans templates.
In these templates there is no Stage defined in FXML, just the actual Scene with UI controlls in it. Something like:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxskuska.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>


That seemed okay, until the first thing I wanted to change - to set the window's name. That's when I realized Scene is not a Window (or a JFrame analogy), but Stage is.
When I tried to wrap all this inside a element, I couldn't set the
fx:controller
attribute to the AnchorPane, because it is not a root element anymore. I even tried to "outsource" it by using
fx:include
in the Stage file, but that just gave me an "Unexpected closing tag:scene" error.


  1. How to define Stage in FXML?

  2. Is Stage the JFX analogy of JFrame?


Answer

FXML essentially just defines ways to create objects (typically via a no-arg constructor) and call setXXX methods on those objects. See "Class instance elements" and "Property elements" in the documentation. So you can readily implement

new Scene()

with

<Scene>...</Scene>

and

new Stage()

with

<Stage>...</Stage>

and

Stage stage = new Stage();
Scene scene = new Scene();
scene.setRoot(new AnchorPane(...));
stage.setScene(scene);

with

<Stage>
    <scene>
        <Scene>
            <root>
                <AnchorPane ><!--...--></AnchorPane>
            </root>
        </Scene>
    </scene>
<Stage>

The fx:controller attribute must go in the root element, as should the namespace info.

So:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<?import javafx.stage.Stage ?>

<Stage title="My Application" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="FXMLDocumentController">

    <scene>

        <Scene>

            <root>

                <AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320">
                    <children>
                        <Button layoutX="126" layoutY="90" text="Click Me!"
                            onAction="#handleButtonAction" fx:id="button" />
                        <Label layoutX="126" layoutY="120" minHeight="16" minWidth="69"
                            fx:id="label" />
                    </children>
                </AnchorPane>

            </root>
        </Scene>

    </scene>

</Stage>

and then you would load this with

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;

public class FXMLStageTest extends Application {

    @Override
    public void start(Stage primaryStage) throws IOException {
        Stage stage = FXMLLoader.load(getClass().getResource("Stage.fxml"));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

If you want the controller specifically to apply to the pane, you can use one FXML file for the Stage and Scene, and then use fx:include elements for the other piece(s). That way you can split the UI into several FXML-controller pairs. It feels a bit redundant to use an FXML file just for a Stage (you may as well do this part in Java, surely), but it is possible:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.*?>
<?import javafx.stage.Stage ?>

<Stage title="My Application" xmlns:fx="http://javafx.com/fxml/1">

    <scene>
        <Scene>
            <root>
                <fx:include source="RootPane.fxml"/>
            </root>
        </Scene>
    </scene>
</Stage>

and then your original FXML file is RootPane.fxml. You could similarly decompose that with further fx:include tags if you needed.