One of the first things you have to do when you create your first JavaFX app is to create a Scene. If you’re anything like me when I first started, you might just blindly slot it into something called a Stage, and maybe set a width and height. Secretly, you’re not really sure what either the scene or the stage are doing.

That’s OK. I made this article s that you don’t have to fumble around in the source code and docs like I did.

The Scene is the highest element of the JavaFX scene graph, containing all of the nodes visible in a window. A Scene can be created without a Stage, but won’t be visible. A scene can be associated with only one Stage at a time.

The scene must be created with a Parent (a layout or a group), which holds the contents of the scene graph.

What will you get from this tutorial?

I’ll go through the important parts about creating, modifying and efficiently using a Scene. There are a lot of ways to use a Scene – some wiser, some slightly more avant garde. My goal is to show you the good ways.

In total, I’ll cover five areas:

Towards the end of the article I’ll go through some best practice about how to use the scene (see the last three bullet points).

What is a Scene

A Scene describes everything that is inside a window in a JavaFX application. Where the Window provides a look and feel that’s specific to the OS that’s driving the application, the Scene is completely yours to style and change.

The window that most people will use is a Stage, which extends the Window class

The scene graph consists of a window (stage), scene and its nodes
The scene graph consists of a window (stage), scene and its nodes

The Scene is responsible for four things:

  • Holding a reference to the Parent that holds the scene’s nodes (this is most often a layout of some type, but can be a Group).
  • Optionally, controlling the size of the window (this can be delegated to the root node, but doesn’t have to be)
  • Setting default higher-level rendering properties (useful for 3D scenes), and the camera (also for 3D)
  • Triggering event handling behind the scenes

Each of these things impacts how the Scene works, and how it needs to be defined. For instance, because the Scene will hold your scene graph, it must to be defined with a root node.

Antialiasing and depth testing correspond to higher-level rendering properties, which can be set as defaults for the scene.

The only aspect of the scene I won’t talk about here is events. I’ll talk about how to use events in the scene (see the last section for that), but not how the Scene itself dispatches events. Events in JavaFX are a such a fundamental topic, I wanted to create a resource that covered them in more depth.

If you want to know more about events, and the Scene’s role in them, check out my article, which takes you through absolutely everything you need to know when it comes to events.

Creating a Scene

Because the Scene is responsible for maintaining the scene graph, it must be created with a Parent object, called the “root”, which contains all nodes in the scene.

Scene(Parent root);

Optionally, it can be created with a width, height and background color:

Scene(Parent root, double width, double height);
Scene(Parent root, double width, double height, Paint fill);
Scene(Parent root, Paint fill);

Finally, and importantly for 3D scenes, the default antialiasing and depth testing properties if a Scene can be defined at creation:

Scene(Parent root, double width, double height, boolean depthBuffer);
Scene(Parent root, double width, double height, boolean depthBuffer, SceneAntialiasing antiAliasing);

How to change, close and clear a Scene

Once your application is up and running, you might need to change the contents of your Scene, close it, or clear it completely! Each of these can be done in several ways, so I’ll take you through each.

How to change a scene

Moving users on from one screen to the next is a common use case for changing the contents of a Scene without changing the window. Especially if the user is working through a multi-page form, the user expectation is that they’ll stay in the same window.

You can accomplish this in a couple of ways, but they essentially all boil down to replacing one part of the scene graph with another. This can be done as high as the root node, or the Scene itself, but doesn’t have to be.

Changing the entire Scene

To remove the entire scene from the window and replace it with different content, invoke setScene() on the Stage object that is currently holding it. Replace the scene with a new one to replace the content.

stage.setScene(newScene);

Bear in mind because you’re replacing the entire Scene object, you’ll also get rid of any event listeners you’ve previously installed. If you don’t want to get rid of the listeners, you might want to consider switching out the root node instead.

Switching out the Root node

Replacing the root node will leave all of your event listeners intact on the current scene, but will still achieve the result of replacing all of the visible contents.

Re-setting the root node can be accomplished by invoking setRoot() on the Scene object with the new Parent object as an argument.

scene.setRoot(newRoot);

Smoother transitions – sliding

Finally, you can go even further down the scene graph to to achieve a smoother transition. By keeping the root node and switching out the contents of the rest of the scene, you can create really attractive transitions, such as sliding.

Closing a window

I’ve included this for completeness here, but it’s worth saying that ‘closing a scene’ is a bit of a misnomer. Closing a scene is a Window-level action. That means it has to be invoked on the Window object, rather the Scene object.

Both the stage and scene can be accessed from anywhere in the scene

With that in mind, I will say that when I first started with JavaFX, I hadn’t worked through distinction between scene and stage. For that reason, I’d often search around trying to work out how to close a scene.

To close the window, invoke getWindow() to access the current window, and invoke hide() on the window object returned.

textField.getScene().getWindow().hide();

Setting the background color of a scene

The default background color of the Scene object is white, but you might have noticed that whenever you create a scene, it looks a light grey color. That’s because the root node also gets styled. And – because the root note is usually a layout pane – it usually gets pulled to fill the entire scene.

The root node is generally stretched to fit the scene, meaning its color will over-write the Scene’s color unless you style it.

Styling the root node

The root node’s style is set using the CSS style class “root”. Modern versions of JavaFX ship with the default stylesheet Modena.css. Here’s a link to the stylesheet that shipped with JavaFX 15: Modena.css. And here’s the excerpt that’s relevant to the background color:

.root {
    /***************************************************************************
     * The main color palette from which the rest of the colors are derived.   *
     **************************************************************************/
    -fx-base: #ececec;
    -fx-background: derive(-fx-base,26.4%);
    ...
}
...

In terms of background color, that means the default background color is a very light grey (exciting stuff). You can either set the background by creating a Background object, or by styling it with CSS.

I’d always suggest linking your FXML with a style sheet, but for brevity, here’s how to do it in Java code.

root.setStyle("-fx-background-color: #81c483");

If you’re planning on styling your layout with a linked CSS style sheet, remember that layout panes don’t have a default style class. You can add one in Java code, or in FXML. Here’s a quick example with a BorderPane (don’t forget to link the stylesheet).

BorderPane borderPane = new BorderPane();
borderPane.getStyleClass().add("border-pane");
borderPane.getStylesheets().add("mystyles.css");
.border-pane{
    -fx-background-color: #81c483;
}

Set the color of the Scene

If you’ve made your layout transparent – or if your root node is a Group, you might still want to color the Scene directly. That’s as simple as passing a color into the setFill() method.

scene.setFill(Color.web("#81c483"));

As a reminder, you won’t be able to see the color of the scene if you’ve got a layout that stretches to fit it. I’ve forgotten this a hundred times over the years, so it bears repeating.

How to get and set the size of a scene

Resizing a scene is actually wickedly simple to do, you just can’t do it with the Scene object. You can’t change the size of the scene because it’s fundamentally tied to being within the stage.

So, to change the size of a Scene, you just need to find the window it’s fitted into, and change the size of that window instead. This will pull the Scene into shape, because the scene will always expand to fill it’s window.

Window window = scene.getWindow();
window.setWidth(450);
window.setHeight(150);

The Scene object itself has accessor methods for the width and height, as well as providing access to them as properties. Just bear in mind you’ll get them as read-only properties, so you can’t change it on the sly.

Scene scene = new Scene(root);
double width = scene.getWidth();
double height = scene.getHeight();
ReadOnlyDoubleProperty wProperty = scene.widthProperty();
ReadOnlyDoubleProperty hProperty = scene.heightProperty();

Listening for Scene-level events like keystroke combinations

You can listen for events like mouse movement and key strokes with event handlers and filters, just as you would on a node.

addEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler);
addEventFilter(EventType<T> eventType, EventHandler<? super T> eventFilter);

Listening for events at the Scene level (rather than on individual nodes) also has substantial benefits. Applying an Event Filter to the Scene absolutely ensures that whatever functionality we want to happen is immediately completed (rather than waiting for other events to fire first).

If you’re wondering why, take a look at my article on Event Filters. It goes through all of the detail on why event filters fire first, and best practice for using them. In this case, we’ll check for the key combination Ctrl+S.

One of the most common use cases for listening to a Scene is core window user expectations such as Open, Save, and Exit. They’re usually represented by commonly-understood key strokes like Ctrl+S or Command-S on Mac.

KeyCombination save = new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN);
scene.addEventFilter(
         KeyEvent.KEY_PRESSED,
         event -> {
            if(save.match(event)) saveSession();
        }
);

Because we’re using an event filter (rather than an event handler) you can also chose whether you want the event to propagate throughout the rest of the scene’s nodes. If you don’t, you just need to invoke event.consume() after you’ve taken whatever action you need.

Of course, if you want to add an event that will fire after every other event in the Scene, you can do so by adding event handlers.

Conclusions

The Scene is one of the most fundamental parts of JavaFX, but often gets overlooked. It holds a reference to the lower scene graph that can be modified or changed, enabling you to swap out scenes as and when required.

Setting the background color of the scene is usually best accomplished by styling the root node. Like the skin of a drum, layouts are stretched to fit the scene, so usually mask the scene’s actual color. Where the layout is transparent, or the root node is a Group, the scene’s background color can be set too.

Finally, event handling on scenes is a powerful way to enforce action at the very start and end of the event dispatch chain. Listening for keystrokes across the scene can be a highly useful way of ensuring user actions such as save and open commands are acted on – regardless of which node currently has focus.