Skip to Content
Course content

313: FXML for Declarative UI

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was mentoring a junior dev who was building a complex telemetry dashboard for a logistics client. He had spent three days writing a single Java class that handled everything: the business logic, the data polling, and about 400 lines of manual layout code. Every time the client asked him to move a button two pixels to the left or change a VBox to an HBox, he had to change the Java code, recompile the entire project, and restart the JVM. He was exhausted, and honestly, the code was a nightmare to read because the actual logic was buried under a mountain of setPadding() and setPrefWidth() calls.

This is exactly why we use FXML. Instead of building your UI procedurally in Java, FXML lets you define the structure of your interface in a declarative XML format. Think of it like HTML for JavaFX. You describe what the UI should look like in one file, and you handle how it behaves in a separate Java class called a Controller. This separation of concerns isn't just about tidiness; it means you can change your entire layout without touching a single line of compiled logic.

Defining the Blueprint with FXML

In an FXML file, you use tags that correspond directly to JavaFX classes. If you want a Label, you use the <Label> tag. If you want a GridPane, you use <GridPane>. I usually recommend starting with a root container that manages the overall flow, then nesting your components inside it.

Let's look at a snippet for a simple System Monitor panel. Instead of calling new Button("Refresh") in Java, we do this:

<VBox spacing="10" xmlns:fx="http://javafx.com/fxml" fx:controller="com.app.monitor.MonitorController">
    <Label text="CPU Usage:" />
    <ProgressBar fx:id="cpuProgress" prefWidth="200.0" />
    <Button text="Refresh Data" onAction="#handleRefresh" />
</VBox>

Notice the fx:id and onAction attributes. These are the hooks. The fx:id tells JavaFX, "I'm going to refer to this specific ProgressBar in my Java code," and the onAction tells it, "When this button is clicked, go find a method called handleRefresh in the controller."

Wiring the Controller with @FXML

Now, the XML is just a blueprint; it doesn't do anything on its own. That's where the Controller class comes in. To connect the two, you use the @FXML annotation. This annotation tells the FXMLLoader that the field or method is intended to be linked to an element in the FXML file.

I've seen developers forget the @FXML annotation and spend an hour wondering why their variables are null. Remember: if the field is private (which it should be for encapsulation), the FXMLLoader needs that annotation to "see" the field and inject the UI component into it.

public class MonitorController {
    @FXML
    private ProgressBar cpuProgress;

    @FXML
    private void handleRefresh() {
        // Logic to poll the system and update the bar
        cpuProgress.setProgress(Math.random()); 
        System.out.println("CPU data refreshed!");
    }
}

When the FXMLLoader loads the FXML file, it instantiates the MonitorController, finds the ProgressBar in the XML with the ID "cpuProgress", and assigns it to the field in the Java class. It's a clean, automated hand-off.

Loading the Interface into the Stage

To actually get this onto the screen, you don't instantiate your layout classes manually. Instead, you use the FXMLLoader class. This is the engine that parses the XML and wires up the controller. I typically wrap this in a Parent object, which then becomes the root of the Scene.

It looks something like this in your main Application class:

FXMLLoader loader = new FXMLLoader(getClass().getResource("monitor.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();

One pro tip: if you find writing XML by hand tedious (and you will), use Scene Builder. It's a drag-and-drop tool provided by Gluon that generates this FXML for you. I use it for 90% of my layout work, then I jump into the raw XML only when I need to do something highly specific or dynamic.




📋 Practical Task

Exercise: Building a Network Configuration Panel

Your task is to create a small "Network Configuration" interface using FXML and a Controller. You need to separate the visual layout from the interaction logic.

Requirements:

  • The FXML File: Create a file named network_config.fxml. Use a VBox as the root. Inside, include:
    • A Label that says "Enter IP Address:".
    • A TextField with an fx:id of ipAddressField.
    • A Button with the text "Connect" and an onAction linked to a method called connectToServer.
    • A Label with an fx:id of statusLabel to display the result.
  • The Controller Class: Create a NetworkController class.
    • Declare the TextField and the Label as private fields using the @FXML annotation.
    • Implement the connectToServer()` method. Inside this method, retrieve the text from the ipAddressField and update the statusLabel to say: "Connecting to [IP Address]...".
  • The Main App: Set up a basic JavaFX Application class that uses FXMLLoader to load network_config.fxml and display it in a Stage.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.