google-site-verification: google61fe8ba583a51912.html
FXML for UI Design in Java

FXML is an XML-based language for designing JavaFX user interfaces, enabling a clean separation between UI layout and application logic. This comprehensive guide covers FXML usage, best practices, and advanced techniques.

Basic FXML Structure

1. Simple FXML Document

hello-view.fxml:

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.text.Font?> <VBox xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" spacing="20.0" alignment="CENTER" style="-fx-padding: 30; -fx-background-color: #f5f5f5;" fx:controller="com.example.controller.HelloController"> <Label text="Welcome to JavaFX!" style="-fx-text-fill: #2c3e50; -fx-font-weight: bold;"> <font> <Font size="24.0" /> </font> </Label> <Button text="Click Me!" onAction="#handleButtonClick" style="-fx-background-color: #3498db; -fx-text-fill: white; -fx-font-size: 14px;" prefWidth="120.0" prefHeight="40.0"/> <Label fx:id="messageLabel" text="Click the button above" style="-fx-text-fill: #7f8c8d;"/> </VBox>

2. Corresponding Controller

HelloController.java:

package com.example.controller; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.Button; import javafx.event.ActionEvent; public class HelloController { @FXML private Label messageLabel; @FXML private Button clickButton; private int clickCount = 0; // Initialize method called after FXML loading @FXML private void initialize() { System.out.println("Controller initialized!"); updateMessage(); } @FXML private void handleButtonClick(ActionEvent event) { clickCount++; updateMessage(); // Change button style after clicks if (clickCount >= 5) { clickButton.setStyle("-fx-background-color: #e74c3c; -fx-text-fill: white;"); } } private void updateMessage() { String message = String.format("Button clicked %d time%s", clickCount, clickCount == 1 ? "" : "s"); messageLabel.setText(message); } // Public method that can be called from main application public void resetCounter() { clickCount = 0; updateMessage(); clickButton.setStyle("-fx-background-color: #3498db; -fx-text-fill: white;"); } }

3. Main Application Class

MainApplication.java:

package com.example; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; public class MainApplication extends Application { private static Stage primaryStage; @Override public void start(Stage stage) throws IOException { primaryStage = stage; showHelloView(); } public void showHelloView() throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/hello-view.fxml")); Parent root = loader.load(); // Get controller instance if needed HelloController controller = loader.getController(); Scene scene = new Scene(root, 600, 400); scene.getStylesheets().add(getClass().getResource("/css/styles.css").toExternalForm()); primaryStage.setTitle("JavaFX FXML Demo"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

Advanced FXML Layouts

4. Complex Layout with Multiple Panes

dashboard-view.fxml:

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.chart.*?> <?import javafx.collections.FXCollections?> <?import javafx.geometry.Insets?> <BorderPane xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.controller.DashboardController"> <!-- Top Menu Bar --> <top> <MenuBar> <menus> <Menu text="File"> <items> <MenuItem text="New" onAction="#handleNew"/> <MenuItem text="Open" onAction="#handleOpen"/> <SeparatorMenuItem/> <MenuItem text="Exit" onAction="#handleExit"/> </items> </Menu> <Menu text="View"> <items> <CheckMenuItem text="Show Charts" selected="true" onAction="#toggleChartsVisibility"/> <CheckMenuItem text="Dark Mode" onAction="#toggleDarkMode"/> </items> </Menu> </menus> </MenuBar> </top> <!-- Left Navigation --> <left> <VBox spacing="10" style="-fx-background-color: #34495e; -fx-padding: 15;"> <Label text="NAVIGATION" style="-fx-text-fill: #ecf0f1; -fx-font-weight: bold; -fx-padding: 0 0 10 0;"/> <Button text="Dashboard" onAction="#showDashboard" style="-fx-background-color: #3498db; -fx-text-fill: white;" maxWidth="Infinity"/> <Button text="Users" onAction="#showUsers" style="-fx-background-color: transparent; -fx-text-fill: #bdc3c7;" maxWidth="Infinity"/> <Button text="Settings" onAction="#showSettings" style="-fx-background-color: transparent; -fx-text-fill: #bdc3c7;" maxWidth="Infinity"/> </VBox> </left> <!-- Center Content --> <center> <TabPane fx:id="mainTabPane" tabClosingPolicy="UNAVAILABLE"> <tabs> <Tab text="Dashboard"> <ScrollPane fitToWidth="true" fitToHeight="true"> <VBox spacing="20" style="-fx-padding: 20;"> <!-- Statistics Cards --> <HBox spacing="20" alignment="CENTER"> <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);" prefWidth="200"> <Label text="Total Users" style="-fx-text-fill: #7f8c8d;"/> <Label fx:id="totalUsersLabel" text="0" style="-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: #2c3e50;"/> </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);" prefWidth="200"> <Label text="Revenue" style="-fx-text-fill: #7f8c8d;"/> <Label fx:id="revenueLabel" text="$0.00" style="-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: #27ae60;"/> </VBox> </HBox> <!-- Chart --> <LineChart fx:id="salesChart" prefHeight="300"> <xAxis> <CategoryAxis label="Month"/> </xAxis> <yAxis> <NumberAxis label="Sales"/> </yAxis> </LineChart> </VBox> </ScrollPane> </Tab> <Tab text="User Management"> <VBox spacing="15" style="-fx-padding: 20;"> <HBox spacing="10" alignment="CENTER_LEFT"> <TextField fx:id="searchField" promptText="Search users..." prefWidth="300"/> <Button text="Search" onAction="#searchUsers"/> <Region HBox.hgrow="ALWAYS"/> <Button text="Add User" onAction="#addUser" style="-fx-background-color: #27ae60; -fx-text-fill: white;"/> </HBox> <TableView fx:id="usersTableView" prefHeight="400"> <columns> <TableColumn text="ID" prefWidth="80"> <cellValueFactory> <PropertyValueFactory property="id"/> </cellValueFactory> </TableColumn> <TableColumn text="Name" prefWidth="150"> <cellValueFactory> <PropertyValueFactory property="name"/> </cellValueFactory> </TableColumn> <TableColumn text="Email" prefWidth="200"> <cellValueFactory> <PropertyValueFactory property="email"/> </cellValueFactory> </TableColumn> <TableColumn text="Role" prefWidth="100"> <cellValueFactory> <PropertyValueFactory property="role"/> </cellValueFactory> </TableColumn> </columns> </TableView> </VBox> </Tab> </tabs> </TabPane> </center> <!-- Bottom Status Bar --> <bottom> <BorderPane style="-fx-background-color: #ecf0f1; -fx-padding: 5;"> <left> <Label fx:id="statusLabel" text="Ready"/> </left> <right> <Label fx:id="timeLabel" text="00:00:00"/> </right> </BorderPane> </bottom> </BorderPane>

5. Advanced Controller

DashboardController.java:

package com.example.controller; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; import javafx.scene.chart.LineChart; import javafx.scene.chart.XYChart; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.util.Duration; import java.net.URL; import java.time.LocalTime; 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 statusLabel; @FXML private Label timeLabel; @FXML private LineChart<String, Number> salesChart; @FXML private TableView<User> usersTableView; @FXML private TextField searchField; @FXML private TabPane mainTabPane; private ObservableList<User> users = FXCollections.observableArrayList(); private Timeline clockTimeline; @Override public void initialize(URL location, ResourceBundle resources) { setupClock(); initializeData(); setupChart(); setupTable(); } private void setupClock() { clockTimeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> { String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); timeLabel.setText(time); })); clockTimeline.setCycleCount(Animation.INDEFINITE); clockTimeline.play(); } private void initializeData() { // Sample data totalUsersLabel.setText("1,247"); revenueLabel.setText("$45,678.90"); users.addAll( new User(1, "John Doe", "[email protected]", "Admin"), new User(2, "Jane Smith", "[email protected]", "User"), new User(3, "Bob Johnson", "[email protected]", "Editor") ); } private void setupChart() { XYChart.Series<String, Number> series = new XYChart.Series<>(); series.setName("Monthly Sales"); 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)); salesChart.getData().add(series); } private void setupTable() { usersTableView.setItems(users); } // Event handlers @FXML private void handleNew(ActionEvent event) { statusLabel.setText("Creating new file..."); } @FXML private void handleOpen(ActionEvent event) { FileChooser fileChooser = new FileChooser(); fileChooser.showOpenDialog(null); } @FXML private void handleExit(ActionEvent event) { System.exit(0); } @FXML private void toggleChartsVisibility(ActionEvent event) { CheckMenuItem menuItem = (CheckMenuItem) event.getSource(); salesChart.setVisible(menuItem.isSelected()); } @FXML private void toggleDarkMode(ActionEvent event) { // Implement dark mode toggle } @FXML private void showDashboard(ActionEvent event) { mainTabPane.getSelectionModel().select(0); } @FXML private void showUsers(ActionEvent event) { mainTabPane.getSelectionModel().select(1); } @FXML private void showSettings(ActionEvent event) { // Show settings tab } @FXML private void searchUsers(ActionEvent event) { String searchText = searchField.getText().toLowerCase(); if (searchText.isEmpty()) { usersTableView.setItems(users); } else { ObservableList<User> filtered = users.filtered(user -> user.getName().toLowerCase().contains(searchText) || user.getEmail().toLowerCase().contains(searchText) ); usersTableView.setItems(filtered); } } @FXML private void addUser(ActionEvent event) { // Open add user dialog statusLabel.setText("Add user dialog opened"); } public void cleanup() { if (clockTimeline != null) { clockTimeline.stop(); } } } // User model class class User { private final IntegerProperty id = new SimpleIntegerProperty(); private final StringProperty name = new SimpleStringProperty(); private final StringProperty email = new SimpleStringProperty(); private final StringProperty role = new SimpleStringProperty(); public User(int id, String name, String email, String role) { setId(id); setName(name); setEmail(email); setRole(role); } // Getters and setters for properties public int getId() { return id.get(); } public void setId(int value) { id.set(value); } public IntegerProperty idProperty() { return id; } public String getName() { return name.get(); } public void setName(String value) { name.set(value); } public StringProperty nameProperty() { return name; } public String getEmail() { return email.get(); } public void setEmail(String value) { email.set(value); } public StringProperty emailProperty() { return name; } public String getRole() { return role.get(); } public void setRole(String value) { role.set(value); } public StringProperty roleProperty() { return role; } }

CSS Styling with FXML

6. External CSS Stylesheet

styles.css:

/* Root styles */ .root { -fx-font-family: "Segoe UI", Arial, sans-serif; -fx-font-size: 14px; } /* Button styles */ .button { -fx-background-radius: 4px; -fx-border-radius: 4px; -fx-cursor: hand; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 4, 0, 0, 2); } .button:hover { -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 6, 0, 0, 3); } .button:pressed { -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 2, 0, 0, 1); } /* Primary button */ .button-primary { -fx-background-color: #3498db; -fx-text-fill: white; } .button-primary:hover { -fx-background-color: #2980b9; } /* Success button */ .button-success { -fx-background-color: #27ae60; -fx-text-fill: white; } .button-success:hover { -fx-background-color: #229954; } /* Text field styles */ .text-field { -fx-background-radius: 4px; -fx-border-radius: 4px; -fx-border-color: #bdc3c7; -fx-border-width: 1px; -fx-padding: 8px; } .text-field:focused { -fx-border-color: #3498db; -fx-effect: dropshadow(three-pass-box, rgba(52, 152, 219, 0.3), 4, 0, 0, 2); } /* Table styles */ .table-view { -fx-border-color: #bdc3c7; -fx-border-width: 1px; } .table-view .column-header { -fx-background-color: #34495e; -fx-text-fill: white; -fx-font-weight: bold; } .table-view .table-row-cell:even { -fx-background-color: #f8f9fa; } .table-view .table-row-cell:odd { -fx-background-color: white; } .table-view .table-row-cell:selected { -fx-background-color: #3498db; -fx-text-fill: white; } /* Card styles */ .card { -fx-background-color: white; -fx-background-radius: 8px; -fx-border-radius: 8px; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 0); -fx-padding: 20px; } /* Label styles */ .label-heading { -fx-font-size: 18px; -fx-font-weight: bold; -fx-text-fill: #2c3e50; } .label-subheading { -fx-font-size: 14px; -fx-text-fill: #7f8c8d; } /* Progress indicator */ .progress-indicator { -fx-progress-color: #3498db; } /* Tab pane styles */ .tab-pane .tab-header-area .tab-header-background { -fx-background-color: #ecf0f1; } .tab-pane .tab { -fx-background-color: #bdc3c7; -fx-background-radius: 4px 4px 0 0; } .tab-pane .tab:selected { -fx-background-color: #3498db; -fx-text-fill: white; }

Custom Components and Reusability

7. Custom FXML Components

user-card.fxml:

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.image.ImageView?> <?import javafx.scene.image.Image?> <VBox xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" style="-fx-background-color: white; -fx-padding: 15; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 0); -fx-spacing: 10;" fx:controller="com.example.controller.UserCardController"> <HBox spacing="10" alignment="CENTER_LEFT"> <ImageView fx:id="avatarImage" fitWidth="40" fitHeight="40" style="-fx-background-radius: 20;"/> <VBox spacing="2"> <Label fx:id="nameLabel" style="-fx-font-weight: bold; -fx-font-size: 14;"/> <Label fx:id="emailLabel" style="-fx-text-fill: #7f8c8d; -fx-font-size: 12;"/> </VBox> </HBox> <HBox spacing="10" alignment="CENTER_RIGHT"> <Button text="Message" onAction="#handleMessage" style="-fx-background-color: #3498db; -fx-text-fill: white; -fx-font-size: 12;"/> <Button text="Profile" onAction="#handleProfile" style="-fx-background-color: transparent; -fx-border-color: #3498db; -fx-text-fill: #3498db; -fx-font-size: 12;"/> </HBox> </VBox>

UserCardController.java:

package com.example.controller; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import java.net.URL; import java.util.ResourceBundle; public class UserCardController implements Initializable { @FXML private Label nameLabel; @FXML private Label emailLabel; @FXML private ImageView avatarImage; private User user; @Override public void initialize(URL location, ResourceBundle resources) { // Default initialization } public void setUser(User user) { this.user = user; updateView(); } private void updateView() { if (user != null) { nameLabel.setText(user.getName()); emailLabel.setText(user.getEmail()); // Set avatar image (you can use a default image) try { Image image = new Image(getClass().getResourceAsStream("/images/avatar.png")); avatarImage.setImage(image); } catch (Exception e) { // Use default image or leave blank } } } @FXML private void handleMessage() { System.out.println("Message user: " + user.getName()); } @FXML private void handleProfile() { System.out.println("View profile: " + user.getName()); } }

8. Using Custom Components in Main FXML

main-view.fxml (with custom components):

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import com.example.component.UserCard?> <ScrollPane xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" fx:controller="com.example.controller.MainController"> <VBox spacing="15" style="-fx-padding: 20;"> <Label text="User Directory" style="-fx-font-size: 24; -fx-font-weight: bold;"/> <FlowPane hgap="15" vgap="15" prefWrapLength="800"> <!-- Custom UserCard components will be added dynamically --> </FlowPane> </VBox> </ScrollPane>

Dynamic FXML Loading

9. Dynamic UI Management

DynamicViewController.java:

package com.example.controller; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.layout.FlowPane; import javafx.scene.layout.VBox; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class DynamicViewController { @FXML private FlowPane userCardsContainer; private List<UserCardController> userCardControllers = new ArrayList<>(); @FXML private void initialize() { loadUserCards(); } private void loadUserCards() { try { List<User> users = getUserData(); // Get users from service for (User user : users) { FXMLLoader loader = new FXMLLoader( getClass().getResource("/fxml/user-card.fxml") ); Node userCard = loader.load(); UserCardController controller = loader.getController(); controller.setUser(user); userCardsContainer.getChildren().add(userCard); userCardControllers.add(controller); } } catch (IOException e) { e.printStackTrace(); } } public void refreshUserCards() { userCardsContainer.getChildren().clear(); userCardControllers.clear(); loadUserCards(); } private List<User> getUserData() { // Sample data - in real app, this would come from a service return List.of( new User(1, "John Doe", "[email protected]", "Admin"), new User(2, "Jane Smith", "[email protected]", "User"), new User(3, "Bob Johnson", "[email protected]", "Editor"), new User(4, "Alice Brown", "[email protected]", "User") ); } }

Best Practices and Patterns

10. FXML Best Practices

Controller Factory Pattern:

public class ControllerFactory { public static <T> T loadFXML(String fxmlPath) throws IOException { FXMLLoader loader = new FXMLLoader( MainApplication.class.getResource(fxmlPath) ); Parent root = loader.load(); return loader.getController(); } public static <T> T loadFXMLWithRoot(String fxmlPath, Parent root) throws IOException { FXMLLoader loader = new FXMLLoader( MainApplication.class.getResource(fxmlPath) ); loader.setRoot(root); loader.load(); return loader.getController(); } } // Usage public class MainController { public void showUserProfile(User user) throws IOException { UserProfileController controller = ControllerFactory.loadFXML("/fxml/user-profile.fxml"); controller.setUser(user); // Show in dialog or new window } }

Event Bus for Communication:

public class EventBus { private static final Map<Class<?>, List<Consumer<?>>> subscribers = new HashMap<>(); public static <T> void subscribe(Class<T> eventType, Consumer<T> handler) { subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler); } public static <T> void publish(T event) { List<Consumer<?>> handlers = subscribers.get(event.getClass()); if (handlers != null) { for (Consumer<?> handler : handlers) { ((Consumer<T>) handler).accept(event); } } } } // Event classes class UserUpdatedEvent { private final User user; public UserUpdatedEvent(User user) { this.user = user; } public User getUser() { return user; } } // Usage in controllers public class UserListController { @FXML private void initialize() { EventBus.subscribe(UserUpdatedEvent.class, this::onUserUpdated); } private void onUserUpdated(UserUpdatedEvent event) { refreshUserList(); } }

Key Benefits of FXML

  1. Separation of Concerns: UI layout separated from application logic
  2. Type Safety: Compile-time checking of FXML references
  3. Tool Support: Visual editors like Scene Builder
  4. Reusability: Custom components and templates
  5. Maintainability: Clear structure and organization
  6. Internationalization: Built-in resource bundle support
  7. CSS Integration: Easy styling and theming

FXML provides a robust, maintainable approach to JavaFX UI development, especially for complex applications with multiple screens and reusable components.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper