Working with FXML Views
FXML (FX Markup Language) is an XML-based language for defining JavaFX user interfaces declaratively. Instead of creating UI elements in Java code, you define them in FXML files, which are then loaded at runtime. This cookbook section explains how FXML works in the Jukebox project.
- FXML file structure and syntax
- Connecting FXML files to controller classes
- Loading FXML with FXMLLoader
- Basic FXML elements and attributes
- The view factory pattern used in Jukebox
FXML File Structure
Let's look at a simple FXML file from the Jukebox project. The main application window is defined in application-window.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<!--
Application Window - Main Container FXML
This is the root FXML file for the application.
-->
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="778.0"
prefWidth="621.0" stylesheets="@/styles/application-window.css"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="jukebox.controller.ApplicationWindowController">
<children>
<!-- Header bar -->
<HBox alignment="CENTER_RIGHT">
<children>
<Label alignment="CENTER" prefWidth="467.0" text="JUKEBOX" HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" onAction="#onHomeButtonClicked" text="GO HOME" />
</children>
</HBox>
<!-- Main container for views -->
<VBox fx:id="container" prefHeight="200.0" prefWidth="100.0" VBox.vgrow="ALWAYS" />
</children>
<padding>
<Insets left="8.0" right="8.0" top="10.0" />
</padding>
</VBox>
XML Declaration
Every FXML file starts with the standard XML declaration:
<?xml version="1.0" encoding="UTF-8"?>
Imports
FXML files use a special import syntax to import JavaFX classes:
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
- Use
<?import class.name?>syntax for each class you want to use - These are not Java imports - they're FXML-specific
- They tell the FXMLLoader which classes are referenced in the file
Root Element
The root element defines the top-level container for your UI. In this case, it's a VBox (vertical box layout). Notice the important attributes:
<VBox alignment="TOP_CENTER" prefHeight="778.0" prefWidth="621.0"
stylesheets="@/styles/application-window.css"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="jukebox.controller.ApplicationWindowController">
Child Elements
FXML files use a hierarchical structure. The root VBox contains <children> which contains the child nodes:
<VBox ...>
<children>
<HBox>...</HBox>
<VBox fx:id="container" ... />
</children>
</VBox>
- Every FXML file has exactly one root element
- Child elements are contained within
<children>tags - The hierarchy matches the JavaFX scene graph structure
- Each element can have its own properties and children
Special FXML Attributes
FXML introduces some special attributes that start with fx::
Connecting Controller to FXML
The fx:controller attribute tells FXMLLoader which controller class to use for this FXML file. The controller class must have public fields with the same names as the fx:id attributes, or use @FXML annotations.
In ApplicationWindowController.java:
public class ApplicationWindowController {
// FXML INJECTED ELEMENTS
// This field matches fx:id="container" in the FXML file
public VBox container;
// ... rest of the controller
}
- When FXMLLoader loads the FXML file, it creates an instance of the controller class
- It then sets the controller's fields to match the nodes with corresponding
fx:idvalues - Fields can be public, or private with
@FXMLannotation
Loading FXML with FXMLLoader
In the Jukebox project, FXML files are loaded using the FXMLLoader class. Each controller has a static createViewInstance() method that handles the loading:
// In ApplicationWindowController.java
public static Region createViewInstance() {
// Create an FXMLLoader to load the view definition from file
FXMLLoader loader = new FXMLLoader(
ApplicationWindowController.class.getResource(
"/fxml/application-window.fxml")
);
try {
// Load the FXML file - this creates all the UI elements
Parent root = loader.load();
// Get the controller instance created by the FXMLLoader
Jukebox.applicationWindowController = loader.getController();
// Return the root region to be used as the scene's root
return (Region) root;
} catch (IOException e) {
// If we can't load the FXML file, throw a runtime exception
throw new RuntimeException(e);
}
}
- Create an
FXMLLoaderinstance with the path to the FXML file - Call
loader.load()- this loads the FXML and creates all UI nodes - The loader automatically creates an instance of the controller class (from
fx:controller) - It injects all
fx:idnodes into the controller's matching fields - Call
loader.getController()to get the controller instance
Notice the path starts with a forward slash: /fxml/application-window.fxml. This is a classpath-relative path. The FXML files are stored in src/resources/fxml/, which is on the classpath, so they're accessible with this path.
Event Handling in FXML
FXML files can directly specify event handlers. In the application window FXML, the button has an onAction attribute:
<Button mnemonicParsing="false" onAction="#onHomeButtonClicked" text="GO HOME" />
This connects the button's action event to a method in the controller:
// In ApplicationWindowController.java
public void onHomeButtonClicked(ActionEvent actionEvent) {
displayHomeView();
}
- The
#prefix tells FXMLLoader this is a method reference - The method name in the controller must match exactly
- The method must have the correct signature (e.g.,
void onActionName(ActionEvent)) - Common event types:
ActionEvent,MouseEvent,KeyEvent
The View Factory Pattern
In the Jukebox project, each view/controller pair uses a factory pattern with a static createViewInstance() method. This provides several benefits:
- Consistent Creation: All views are created the same way
- Centralized Access: Controllers are stored in static fields for easy access
- Lazy Initialization: Views are only created when first needed
- Encapsulation: The creation logic is hidden from the rest of the application
Example from ApplicationWindowController:
public void initialize() {
// Create and cache all views
chooseAlbumView = ChooseAlbumController.createViewInstance();
homeScreenView = HomeScreenController.createViewInstance();
playAlbumView = PlayAlbumController.createViewInstance();
adminPanelView = AdminPanelController.createViewInstance();
// Display the home view
displayHomeView();
}
Creating Your Own FXML View
Let's create a simple FXML file for a new view:
Step 1: Create the FXML file
Create src/resources/fxml/my-view.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" spacing="20"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="myapp.MyController">
<padding>
<Insets bottom="20" left="20" right="20" top="20"/>
</padding>
<children>
<Label fx:id="welcomeLabel" text="Welcome to My View!" />
<Button fx:id="actionButton" onAction="#handleButtonClick" text="Click Me" />
</children>
</VBox>
Step 2: Create the Controller
Create src/java/myapp/MyController.java:
package myapp;
import javafx.event.ActionEvent;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.fxml.FXMLLoader;
import java.io.IOException;
public class MyController {
// Injected from FXML
public Label welcomeLabel;
public javafx.scene.control.Button actionButton;
public void handleButtonClick(ActionEvent event) {
welcomeLabel.setText("Button Clicked!");
}
public static Region createViewInstance() {
FXMLLoader loader = new FXMLLoader(
MyController.class.getResource("/fxml/my-view.fxml")
);
try {
return loader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Step 3: Use the View
From your main application or another controller:
Region myView = MyController.createViewInstance();
// Add to your scene or container
container.getChildren().add(myView);
Try This:
- Open
application-window.fxmland look at the structure - Find the
fx:controllerattribute and verify it matchesApplicationWindowController - Find a node with an
fx:idand then find the matching field in the controller class - Find a button with
onActionand find the corresponding method in the controller
Common FXML Patterns in Jukebox
1. Layout Containers
Jukebox uses various layout containers:
VBox- Vertical arrangement of children (used inapplication-window.fxml)HBox- Horizontal arrangement of childrenSplitPane- Resizable sections with divider (used inadmin-panel-view.fxml)
2. UI Controls
Common controls used in the Jukebox FXML files:
Label- Display textButton- Clickable button with action handlerListView- Display a list of items (used inadmin-panel-view.fxml)TableView- Display tabular data (used inchoose-album-view.fxml)TextField- Text input (used for editing album/song properties)ComboBox- Dropdown selection (used for genre selection)
3. Stylesheets
FXML files can reference CSS stylesheets using the stylesheets attribute:
<VBox stylesheets="@/styles/application-window.css" ...>
Key Takeaways
- FXML is an XML-based language for defining JavaFX UIs
- FXML files have a root element and hierarchical child elements
- Import JavaFX classes with
<?import class.name?> - Connect to a controller with
fx:controller - Identify nodes for injection with
fx:id - Handle events with
onActionor similar attributes - Load FXML with
FXMLLoader - The factory pattern (
createViewInstance()) provides clean view creation