Introduction
Gluon Scene Builder is a visual layout tool that allows developers to quickly design JavaFX application user interfaces without writing FXML manually. This article explores how to use Scene Builder effectively, integrate it with Java projects, and create modern, responsive UIs for JavaFX applications.
What is Gluon Scene Builder?
Gluon Scene Builder is a WYSIWYG (What You See Is What You Get) designer for JavaFX that:
- Generates FXML files visually
- Provides drag-and-drop component placement
- Allows CSS styling integration
- Supports controller binding
- Enables responsive design
Setting Up Scene Builder
Installation
Option 1: Standalone Application
- Download from Gluon HQ
- Install for your platform (Windows, macOS, Linux)
Option 2: IDE Integration
- IntelliJ IDEA: Built-in support or install JavaFX plugin
- Eclipse: Install e(fx)clipse plugin
- NetBeans: Built-in JavaFX support
Maven Configuration
<!-- pom.xml -->
<properties>
<javafx.version>21</javafx.version>
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
</properties>
<dependencies>
<!-- JavaFX Controls -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- Gluon Mobile for cross-platform -->
<dependency>
<groupId>com.gluonhq</groupId>
<artifactId>charm-glisten</artifactId>
<version>6.2.0</version>
</dependency>
<!-- ControlsFX for additional components -->
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>11.1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>${javafx.maven.plugin.version}</version>
<configuration>
<mainClass>com.example.MainApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>
Basic Scene Builder Workflow
1. Creating a Simple Login Form
FXML Generated by Scene Builder (login.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.control.PasswordField?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.GridPane?> <VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" alignment="CENTER" spacing="20.0" style="-fx-background-color: #f4f4f4;" fx:controller="com.example.controller.LoginController"> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> <Label text="Welcome Back" style="-fx-font-size: 24; -fx-font-weight: bold;" /> <GridPane hgap="10.0" vgap="10.0"> <columnConstraints> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="200.0" /> </columnConstraints> <rowConstraints> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> </rowConstraints> <Label text="Username:" GridPane.columnIndex="0" GridPane.rowIndex="0" /> <TextField fx:id="usernameField" GridPane.columnIndex="1" GridPane.rowIndex="0" /> <Label text="Password:" GridPane.columnIndex="0" GridPane.rowIndex="1" /> <PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="1" /> </GridPane> <HBox spacing="10.0" alignment="CENTER"> <Button fx:id="loginButton" text="Login" onAction="#handleLogin" style="-fx-background-color: #007bff; -fx-text-fill: white;" /> <Button fx:id="cancelButton" text="Cancel" onAction="#handleCancel" style="-fx-background-color: #6c757d; -fx-text-fill: white;" /> </HBox> <Label fx:id="messageLabel" style="-fx-text-fill: red;" /> </VBox>
Corresponding Controller (LoginController.java):
package com.example.controller;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
public class LoginController {
@FXML
private TextField usernameField;
@FXML
private PasswordField passwordField;
@FXML
private Button loginButton;
@FXML
private Button cancelButton;
@FXML
private Label messageLabel;
@FXML
private void initialize() {
// Initialization logic
loginButton.setDisable(true);
// Add listeners for real-time validation
usernameField.textProperty().addListener((observable, oldValue, newValue) -> {
validateFields();
});
passwordField.textProperty().addListener((observable, oldValue, newValue) -> {
validateFields();
});
}
@FXML
private void handleLogin() {
String username = usernameField.getText();
String password = passwordField.getText();
// Simple authentication logic
if ("admin".equals(username) && "password".equals(password)) {
messageLabel.setText("Login successful!");
messageLabel.setStyle("-fx-text-fill: green;");
// Show success alert
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Login Success");
alert.setHeaderText(null);
alert.setContentText("Welcome, " + username + "!");
alert.showAndWait();
} else {
messageLabel.setText("Invalid username or password!");
messageLabel.setStyle("-fx-text-fill: red;");
}
}
@FXML
private void handleCancel() {
usernameField.clear();
passwordField.clear();
messageLabel.setText("");
}
private void validateFields() {
boolean isValid = !usernameField.getText().trim().isEmpty()
&& !passwordField.getText().trim().isEmpty();
loginButton.setDisable(!isValid);
}
}
Main Application Class:
package com.example;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// Load FXML file created with Scene Builder
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/login.fxml"));
Parent root = loader.load();
// Set up the scene
Scene scene = new Scene(root, 400, 300);
// Apply CSS styles
scene.getStylesheets().add(getClass().getResource("/css/styles.css").toExternalForm());
// Configure the stage
primaryStage.setTitle("JavaFX Login Demo");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Advanced UI Components
2. Dashboard with Multiple Panels
dashboard.fxml:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.chart.*?> <BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.controller.DashboardController"> <!-- Top Menu Bar --> <top> <MenuBar> <Menu text="File"> <MenuItem text="New" onAction="#handleNew"/> <MenuItem text="Open" onAction="#handleOpen"/> <SeparatorMenuItem/> <MenuItem text="Exit" onAction="#handleExit"/> </Menu> <Menu text="Edit"> <MenuItem text="Preferences" onAction="#handlePreferences"/> </Menu> <Menu text="View"> <CheckMenuItem text="Dark Mode" onAction="#toggleDarkMode"/> </Menu> </MenuBar> </top> <!-- Left Sidebar --> <left> <VBox spacing="10" style="-fx-background-color: #2c3e50; -fx-padding: 15;"> <Button text="Dashboard" style="-fx-text-fill: white; -fx-background-color: transparent;" onAction="#showDashboard"/> <Button text="Analytics" style="-fx-text-fill: white; -fx-background-color: transparent;" onAction="#showAnalytics"/> <Button text="Settings" style="-fx-text-fill: white; -fx-background-color: transparent;" onAction="#showSettings"/> <Region VBox.vgrow="ALWAYS"/> <Button text="Logout" style="-fx-text-fill: white; -fx-background-color: #e74c3c;" onAction="#handleLogout"/> </VBox> </left> <!-- Center Content --> <center> <TabPane fx:id="mainTabPane" tabClosingPolicy="UNAVAILABLE"> <Tab text="Dashboard"> <ScrollPane fitToWidth="true"> <VBox spacing="20" style="-fx-padding: 20;"> <!-- Statistics Cards --> <HBox spacing="20"> <VBox style="-fx-background-color: white; -fx-padding: 20; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 0);"> <Label text="Total Users" style="-fx-font-size: 14; -fx-text-fill: #7f8c8d;"/> <Label fx:id="totalUsersLabel" text="1,234" style="-fx-font-size: 24; -fx-font-weight: bold;"/> </VBox> <VBox style="-fx-background-color: white; -fx-padding: 20; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 0);"> <Label text="Revenue" style="-fx-font-size: 14; -fx-text-fill: #7f8c8d;"/> <Label fx:id="revenueLabel" text="$12,345" style="-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: #27ae60;"/> </VBox> <VBox style="-fx-background-color: white; -fx-padding: 20; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 0);"> <Label text="Growth" style="-fx-font-size: 14; -fx-text-fill: #7f8c8d;"/> <Label fx:id="growthLabel" text="+12.5%" style="-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: #e74c3c;"/> </VBox> </HBox> <!-- Charts --> <HBox spacing="20"> <VBox> <Label text="Monthly Sales" style="-fx-font-size: 16; -fx-font-weight: bold;"/> <LineChart fx:id="salesChart"> <xAxis> <CategoryAxis label="Month"/> </xAxis> <yAxis> <NumberAxis label="Sales"/> </yAxis> </LineChart> </VBox> <VBox> <Label text="User Distribution" style="-fx-font-size: 16; -fx-font-weight: bold;"/> <PieChart fx:id="userPieChart"/> </VBox> </HBox> <!-- Data Table --> <VBox> <Label text="Recent Activity" style="-fx-font-size: 16; -fx-font-weight: bold;"/> <TableView fx:id="activityTable"> <columns> <TableColumn text="User" prefWidth="150"/> <TableColumn text="Action" prefWidth="200"/> <TableColumn text="Date" prefWidth="150"/> </columns> </TableView> </VBox> </VBox> </ScrollPane> </Tab> <Tab text="Analytics"> <Label text="Analytics content goes here..." style="-fx-padding: 20;"/> </Tab> <Tab text="Settings"> <Label text="Settings content goes here..." style="-fx-padding: 20;"/> </Tab> </TabPane> </center> <!-- Status Bar --> <bottom> <HBox style="-fx-background-color: #ecf0f1; -fx-padding: 5 10;" spacing="10"> <Label fx:id="statusLabel" text="Ready"/> <Region HBox.hgrow="ALWAYS"/> <Label fx:id="timeLabel" text=""/> </HBox> </bottom> </BorderPane>
DashboardController.java:
package com.example.controller;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.util.Duration;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ResourceBundle;
public class DashboardController implements Initializable {
@FXML private Label totalUsersLabel;
@FXML private Label revenueLabel;
@FXML private Label growthLabel;
@FXML private Label statusLabel;
@FXML private Label timeLabel;
@FXML private LineChart<String, Number> salesChart;
@FXML private PieChart userPieChart;
@FXML private TableView<Activity> activityTable;
@FXML private TabPane mainTabPane;
private Timeline clockTimeline;
@Override
public void initialize(URL location, ResourceBundle resources) {
setupClock();
initializeCharts();
initializeTable();
loadSampleData();
}
private void setupClock() {
// Update time every second
clockTimeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> {
String time = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
timeLabel.setText(time);
}));
clockTimeline.setCycleCount(Animation.INDEFINITE);
clockTimeline.play();
}
private void initializeCharts() {
// Setup sales chart
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Monthly Sales");
salesChart.getData().add(series);
salesChart.setLegendVisible(false);
salesChart.setAnimated(true);
// Setup pie chart
userPieChart.setLabelsVisible(true);
userPieChart.setLegendVisible(false);
}
private void initializeTable() {
// Table would be configured here
}
private void loadSampleData() {
// Load sample data for demonstration
loadSampleChartData();
loadSamplePieData();
updateStatistics();
}
private void loadSampleChartData() {
XYChart.Series<String, Number> series = salesChart.getData().get(0);
series.getData().add(new XYChart.Data<>("Jan", 1000));
series.getData().add(new XYChart.Data<>("Feb", 1500));
series.getData().add(new XYChart.Data<>("Mar", 1200));
series.getData().add(new XYChart.Data<>("Apr", 1800));
series.getData().add(new XYChart.Data<>("May", 2000));
series.getData().add(new XYChart.Data<>("Jun", 2500));
}
private void loadSamplePieData() {
userPieChart.getData().addAll(
new PieChart.Data("Desktop", 45),
new PieChart.Data("Mobile", 35),
new PieChart.Data("Tablet", 20)
);
}
private void updateStatistics() {
totalUsersLabel.setText("1,234");
revenueLabel.setText("$12,345");
growthLabel.setText("+12.5%");
}
// Menu actions
@FXML
private void handleNew() {
statusLabel.setText("Creating new item...");
}
@FXML
private void handleOpen() {
statusLabel.setText("Opening file...");
}
@FXML
private void handleExit() {
System.exit(0);
}
@FXML
private void handlePreferences() {
statusLabel.setText("Opening preferences...");
}
@FXML
private void toggleDarkMode() {
statusLabel.setText("Toggling dark mode...");
}
@FXML
private void showDashboard() {
mainTabPane.getSelectionModel().select(0);
}
@FXML
private void showAnalytics() {
mainTabPane.getSelectionModel().select(1);
}
@FXML
private void showSettings() {
mainTabPane.getSelectionModel().select(2);
}
@FXML
private void handleLogout() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Logout");
alert.setHeaderText("Confirm Logout");
alert.setContentText("Are you sure you want to logout?");
alert.showAndWait().ifPresent(response -> {
if (response == ButtonType.OK) {
// Close application or return to login
System.exit(0);
}
});
}
// Clean up resources
public void cleanup() {
if (clockTimeline != null) {
clockTimeline.stop();
}
}
// Data model for table
public static class Activity {
private final String user;
private final String action;
private final String date;
public Activity(String user, String action, String date) {
this.user = user;
this.action = action;
this.date = date;
}
public String getUser() { return user; }
public String getAction() { return action; }
public String getDate() { return date; }
}
}
CSS Styling with Scene Builder
Modern CSS Styles (styles.css)
/* styles.css */
/* Root styles */
.root {
-fx-font-family: "Segoe UI", Arial, sans-serif;
-fx-font-size: 14px;
}
/* Button Styles */
.button {
-fx-background-radius: 5;
-fx-border-radius: 5;
-fx-padding: 8 16;
-fx-cursor: hand;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
}
.button:hover {
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 8, 0, 0, 3);
}
.button:pressed {
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 2, 0, 0, 1);
}
/* Primary Button */
.primary-button {
-fx-background-color: #007bff;
-fx-text-fill: white;
}
.primary-button:hover {
-fx-background-color: #0056b3;
}
/* Success Button */
.success-button {
-fx-background-color: #28a745;
-fx-text-fill: white;
}
.success-button:hover {
-fx-background-color: #1e7e34;
}
/* Danger Button */
.danger-button {
-fx-background-color: #dc3545;
-fx-text-fill: white;
}
.danger-button:hover {
-fx-background-color: #bd2130;
}
/* Text Field Styles */
.text-field, .password-field {
-fx-background-radius: 3;
-fx-border-radius: 3;
-fx-border-color: #ced4da;
-fx-padding: 8;
}
.text-field:focused, .password-field:focused {
-fx-border-color: #007bff;
-fx-effect: dropshadow(three-pass-box, rgba(0,123,255,0.25), 5, 0, 0, 0);
}
/* Label Styles */
.title-label {
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
}
.subtitle-label {
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-text-fill: #34495e;
}
/* Card Styles */
.card {
-fx-background-color: white;
-fx-background-radius: 8;
-fx-border-radius: 8;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 0);
-fx-padding: 20;
}
.card:hover {
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.15), 15, 0, 0, 0);
}
/* Tab Pane Styles */
.tab-pane {
-fx-tab-min-width: 100px;
-fx-tab-min-height: 40px;
}
.tab {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-padding: 10 20;
}
.tab:selected {
-fx-background-color: #007bff;
-fx-text-fill: white;
}
.tab:hover {
-fx-background-color: #e9ecef;
}
.tab:selected:hover {
-fx-background-color: #0056b3;
}
/* Table Styles */
.table-view {
-fx-background-color: transparent;
-fx-border-color: #dee2e6;
-fx-border-radius: 5;
}
.table-view .column-header {
-fx-background-color: #f8f9fa;
-fx-border-color: #dee2e6;
-fx-font-weight: bold;
}
.table-row-cell:even {
-fx-background-color: #f8f9fa;
}
.table-row-cell:odd {
-fx-background-color: white;
}
.table-row-cell:selected {
-fx-background-color: #007bff;
-fx-text-fill: white;
}
/* Chart Styles */
.chart {
-fx-background-color: transparent;
}
.chart-plot-background {
-fx-background-color: transparent;
}
.chart-title {
-fx-font-size: 16px;
-fx-font-weight: bold;
}
/* Progress Bar */
.progress-bar {
-fx-accent: #007bff;
}
.progress-bar .track {
-fx-background-color: #e9ecef;
-fx-background-radius: 5;
}
.progress-bar .bar {
-fx-background-radius: 5;
}
/* Custom Toggle Switch */
.toggle-switch {
-fx-background-color: #6c757d;
-fx-background-radius: 15;
-fx-pref-width: 50;
-fx-pref-height: 25;
}
.toggle-switch:selected {
-fx-background-color: #007bff;
}
/* Dark Theme */
.dark-theme {
-fx-background-color: #2c3e50;
-fx-text-fill: #ecf0f1;
}
.dark-theme .card {
-fx-background-color: #34495e;
-fx-text-fill: #ecf0f1;
}
.dark-theme .text-field, .dark-theme .password-field {
-fx-background-color: #34495e;
-fx-text-fill: #ecf0f1;
-fx-border-color: #7f8c8d;
}
/* Responsive Design */
@media (max-width: 600px) {
.responsive-hbox {
-fx-orientation: vertical;
}
.responsive-vbox {
-fx-spacing: 10;
}
}
Custom Components with Scene Builder
3. Creating Reusable Custom Components
User Card Component (user-card.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.image.Image?> <?import javafx.scene.image.ImageView?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <HBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" styleClass="card" spacing="15.0" fx:controller="com.example.component.UserCardController"> <padding> <Insets bottom="15.0" left="15.0" right="15.0" top="15.0" /> </padding> <!-- User Avatar --> <ImageView fx:id="avatarImageView" fitHeight="60.0" fitWidth="60.0" style="-fx-background-radius: 30;"/> <!-- User Info --> <VBox spacing="5.0" HBox.hgrow="ALWAYS"> <Label fx:id="nameLabel" style="-fx-font-size: 16; -fx-font-weight: bold;"/> <Label fx:id="emailLabel" style="-fx-text-fill: #7f8c8d;"/> <Label fx:id="roleLabel" style="-fx-font-size: 12; -fx-text-fill: #95a5a6;"/> </VBox> <!-- Actions --> <VBox spacing="5.0"> <Button fx:id="editButton" text="Edit" onAction="#handleEdit" styleClass="primary-button" prefWidth="80"/> <Button fx:id="deleteButton" text="Delete" onAction="#handleDelete" styleClass="danger-button" prefWidth="80"/> </VBox> </HBox>
UserCardController.java:
package com.example.component;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class UserCardController {
@FXML private ImageView avatarImageView;
@FXML private Label nameLabel;
@FXML private Label emailLabel;
@FXML private Label roleLabel;
@FXML private Button editButton;
@FXML private Button deleteButton;
private User user;
private UserCardListener listener;
@FXML
private void initialize() {
// Default avatar
avatarImageView.setImage(new Image(
getClass().getResourceAsStream("/images/default-avatar.png")));
}
public void setUser(User user) {
this.user = user;
updateUI();
}
public void setListener(UserCardListener listener) {
this.listener = listener;
}
private void updateUI() {
if (user != null) {
nameLabel.setText(user.getName());
emailLabel.setText(user.getEmail());
roleLabel.setText(user.getRole());
if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) {
try {
Image avatar = new Image(user.getAvatarUrl());
avatarImageView.setImage(avatar);
} catch (Exception e) {
// Use default avatar if loading fails
}
}
}
}
@FXML
private void handleEdit() {
if (listener != null) {
listener.onEditUser(user);
}
}
@FXML
private void handleDelete() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Delete User");
alert.setHeaderText("Confirm Deletion");
alert.setContentText("Are you sure you want to delete " + user.getName() + "?");
alert.showAndWait().ifPresent(response -> {
if (response.getText().equals("OK")) {
if (listener != null) {
listener.onDeleteUser(user);
}
}
});
}
// User model
public static class User {
private final String name;
private final String email;
private final String role;
private final String avatarUrl;
public User(String name, String email, String role, String avatarUrl) {
this.name = name;
this.email = email;
this.role = role;
this.avatarUrl = avatarUrl;
}
public String getName() { return name; }
public String getEmail() { return email; }
public String getRole() { return role; }
public String getAvatarUrl() { return avatarUrl; }
}
// Listener interface for user actions
public interface UserCardListener {
void onEditUser(User user);
void onDeleteUser(User user);
}
}
Integration with Build Tools
Maven Build Configuration for JavaFX
<!-- Complete Maven configuration -->
<profiles>
<profile>
<id>javafx</id>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.example.MainApp</mainClass>
<options>
<option>--enable-preview</option>
</options>
</configuration>
</plugin>
<!-- Copy FXML and CSS resources -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.fxml</include>
<include>**/*.css</include>
<include>**/*.png</include>
<include>**/*.jpg</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Best Practices
1. MVC Architecture with Scene Builder
// Model
public class UserModel {
private final StringProperty name = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
private final ObjectProperty<LocalDate> birthDate = new SimpleObjectProperty<>();
// Properties for binding
public StringProperty nameProperty() { return name; }
public StringProperty emailProperty() { return email; }
public ObjectProperty<LocalDate> birthDateProperty() { return birthDate; }
// Getters and setters
public String getName() { return name.get(); }
public void setName(String name) { this.name.set(name); }
public String getEmail() { return email.get(); }
public void setEmail(String email) { this.email.set(email); }
public LocalDate getBirthDate() { return birthDate.get(); }
public void setBirthDate(LocalDate birthDate) { this.birthDate.set(birthDate); }
}
// Service
public class UserService {
public ObservableList<UserModel> loadUsers() {
// Load users from database or API
return FXCollections.observableArrayList();
}
public void saveUser(UserModel user) {
// Save user logic
}
}
// Controller using Scene Builder FXML
public class UserManagementController implements Initializable {
@FXML private TableView<UserModel> userTable;
@FXML private TableColumn<UserModel, String> nameColumn;
@FXML private TableColumn<UserModel, String> emailColumn;
@FXML private TextField searchField;
private final UserService userService = new UserService();
private final ObservableList<UserModel> users = FXCollections.observableArrayList();
@Override
public void initialize(URL location, ResourceBundle resources) {
setupTable();
loadUsers();
setupSearch();
}
private void setupTable() {
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
emailColumn.setCellValueFactory(new PropertyValueFactory<>("email"));
userTable.setItems(users);
}
private void loadUsers() {
users.setAll(userService.loadUsers());
}
private void setupSearch() {
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
filterUsers(newValue);
});
}
private void filterUsers(String searchText) {
if (searchText == null || searchText.isEmpty()) {
userTable.setItems(users);
} else {
ObservableList<UserModel> filtered = users.filtered(user ->
user.getName().toLowerCase().contains(searchText.toLowerCase()) ||
user.getEmail().toLowerCase().contains(searchText.toLowerCase())
);
userTable.setItems(filtered);
}
}
}
2. Responsive Design
public class ResponsiveLayout {
public static void makeResponsive(Node node) {
// Add listener for scene changes
node.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene != null) {
makeNodeResponsive(node, newScene);
}
});
}
private static void makeNodeResponsive(Node node, Scene scene) {
// Responsive behavior based on scene width
scene.widthProperty().addListener((obs, oldWidth, newWidth) -> {
applyResponsiveRules(node, newWidth.doubleValue(), scene.getHeight());
});
scene.heightProperty().addListener((obs, oldHeight, newHeight) -> {
applyResponsiveRules(node, scene.getWidth(), newHeight.doubleValue());
});
}
private static void applyResponsiveRules(Node node, double width, double height) {
if (node instanceof Pane) {
Pane pane = (Pane) node;
// Mobile breakpoint
if (width < 768) {
pane.setStyle("-fx-padding: 10; -fx-spacing: 10;");
}
// Tablet breakpoint
else if (width < 1024) {
pane.setStyle("-fx-padding: 20; -fx-spacing: 15;");
}
// Desktop
else {
pane.setStyle("-fx-padding: 30; -fx-spacing: 20;");
}
}
}
}
Conclusion
Gluon Scene Builder significantly accelerates JavaFX development by:
- Rapid Prototyping - Visual design reduces development time
- Maintainable Code - Separation of UI (FXML) and logic (Java)
- Consistent Design - Reusable components and CSS styling
- Team Collaboration - Designers and developers can work together
- Modern UIs - Access to rich JavaFX components and effects
Key Benefits:
- Drag-and-drop interface design
- Real-time preview of UI changes
- CSS integration for styling
- Controller binding for business logic
- Cross-platform compatibility
Best Practices:
- Use MVC pattern with FXML controllers
- Implement responsive design principles
- Create reusable custom components
- Use CSS for consistent styling
- Follow JavaFX binding patterns for reactive UIs
Scene Builder empowers Java developers to create modern, professional-looking applications with the power and performance of JavaFX while maintaining productivity through visual design tools.