Article
JavaFX is the modern successor to Swing for building rich desktop applications in Java. It provides a powerful set of APIs for creating sophisticated GUIs with advanced graphics, multimedia, and styling capabilities. This guide covers JavaFX fundamentals, from basic setup to creating interactive applications.
JavaFX Architecture Overview
JavaFX follows a hierarchical scene graph architecture:
- Stage: The top-level container (window)
- Scene: Container for all content
- Nodes: UI components (buttons, labels, layouts)
- Properties & Binding: Reactive data handling
Project Setup and Dependencies
Maven Dependencies
<properties>
<javafx.version>17</javafx.version>
</properties>
<dependencies>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.example.MainApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>
Gradle Dependencies
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.13'
}
javafx {
version = '17'
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = 'com.example.MainApp'
Basic JavaFX Application Structure
1. Minimal JavaFX Application
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BasicApp extends Application {
@Override
public void start(Stage primaryStage) {
// Create a label
Label label = new Label("Hello, JavaFX!");
// Create a layout container
StackPane root = new StackPane();
root.getChildren().add(label);
// Create scene with specified dimensions
Scene scene = new Scene(root, 400, 300);
// Set up the stage
primaryStage.setTitle("My First JavaFX App");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
2. Enhanced Application with CSS Styling
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class StyledApp extends Application {
private int clickCount = 0;
private Label countLabel;
@Override
public void start(Stage primaryStage) {
// Create UI components
Label titleLabel = new Label("Welcome to JavaFX!");
titleLabel.getStyleClass().add("title-label");
countLabel = new Label("Clicks: 0");
Button clickButton = new Button("Click Me!");
clickButton.setOnAction(e -> handleButtonClick());
TextField nameField = new TextField();
nameField.setPromptText("Enter your name");
Button greetButton = new Button("Greet");
greetButton.setOnAction(e -> showGreeting(nameField.getText()));
// Create layout
VBox root = new VBox(20); // 20px spacing between nodes
root.setPadding(new Insets(25));
root.setAlignment(Pos.TOP_CENTER);
root.getChildren().addAll(titleLabel, countLabel, clickButton, nameField, greetButton);
// Create scene with CSS
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm());
// Configure stage
primaryStage.setTitle("Styled JavaFX Application");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
private void handleButtonClick() {
clickCount++;
countLabel.setText("Clicks: " + clickCount);
}
private void showGreeting(String name) {
if (name != null && !name.trim().isEmpty()) {
countLabel.setText("Hello, " + name + "!");
} else {
countLabel.setText("Please enter a name!");
}
}
public static void main(String[] args) {
launch(args);
}
}
styles.css:
.root {
-fx-background-color: linear-gradient(to bottom, #667eea 0%, #764ba2 100%);
}
.title-label {
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-text-fill: white;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.5), 10, 0.5, 2, 2);
}
.button {
-fx-background-color: #4CAF50;
-fx-text-fill: white;
-fx-font-weight: bold;
-fx-background-radius: 15;
-fx-padding: 10 20;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 5, 0, 0, 2);
}
.button:hover {
-fx-background-color: #45a049;
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
.button:pressed {
-fx-background-color: #3d8b40;
}
.text-field {
-fx-background-radius: 10;
-fx-border-radius: 10;
-fx-border-color: #cccccc;
-fx-padding: 8 12;
-fx-font-size: 14px;
}
.label {
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 3, 0.5, 1, 1);
}
Core JavaFX Controls
Common UI Controls Example
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class ControlsDemo extends Application {
@Override
public void start(Stage primaryStage) {
// Create various controls
Label nameLabel = new Label("Name:");
TextField nameField = new TextField();
nameField.setPromptText("Enter your name");
Label ageLabel = new Label("Age:");
Spinner<Integer> ageSpinner = new Spinner<>(1, 100, 25);
ageSpinner.setEditable(true);
Label genderLabel = new Label("Gender:");
ToggleGroup genderGroup = new ToggleGroup();
RadioButton maleRadio = new RadioButton("Male");
RadioButton femaleRadio = new RadioButton("Female");
maleRadio.setToggleGroup(genderGroup);
femaleRadio.setToggleGroup(genderGroup);
Label countryLabel = new Label("Country:");
ComboBox<String> countryCombo = new ComboBox<>();
countryCombo.getItems().addAll("USA", "Canada", "UK", "Australia", "Germany");
countryCombo.setValue("USA");
Label hobbiesLabel = new Label("Hobbies:");
CheckBox sportsCheck = new CheckBox("Sports");
CheckBox readingCheck = new CheckBox("Reading");
CheckBox musicCheck = new CheckBox("Music");
CheckBox travelCheck = new CheckBox("Travel");
Label commentsLabel = new Label("Comments:");
TextArea commentsArea = new TextArea();
commentsArea.setPromptText("Enter your comments here...");
commentsArea.setPrefRowCount(3);
Button submitButton = new Button("Submit");
Button clearButton = new Button("Clear");
// Create layout
GridPane grid = new GridPane();
grid.setPadding(new Insets(20));
grid.setHgap(10);
grid.setVgap(15);
// Add controls to grid
grid.add(nameLabel, 0, 0);
grid.add(nameField, 1, 0);
grid.add(ageLabel, 0, 1);
grid.add(ageSpinner, 1, 1);
grid.add(genderLabel, 0, 2);
GridPane genderPane = new GridPane();
genderPane.setHgap(10);
genderPane.add(maleRadio, 0, 0);
genderPane.add(femaleRadio, 1, 0);
grid.add(genderPane, 1, 2);
grid.add(countryLabel, 0, 3);
grid.add(countryCombo, 1, 3);
grid.add(hobbiesLabel, 0, 4);
GridPane hobbiesPane = new GridPane();
hobbiesPane.setHgap(10);
hobbiesPane.add(sportsCheck, 0, 0);
hobbiesPane.add(readingCheck, 1, 0);
hobbiesPane.add(musicCheck, 0, 1);
hobbiesPane.add(travelCheck, 1, 1);
grid.add(hobbiesPane, 1, 4);
grid.add(commentsLabel, 0, 5);
grid.add(commentsArea, 1, 5);
GridPane buttonPane = new GridPane();
buttonPane.setHgap(10);
buttonPane.add(submitButton, 0, 0);
buttonPane.add(clearButton, 1, 0);
grid.add(buttonPane, 1, 6);
// Event handling
submitButton.setOnAction(e -> handleSubmit(
nameField.getText(),
ageSpinner.getValue(),
getSelectedGender(genderGroup),
countryCombo.getValue(),
getSelectedHobbies(sportsCheck, readingCheck, musicCheck, travelCheck),
commentsArea.getText()
));
clearButton.setOnAction(e -> {
nameField.clear();
ageSpinner.getValueFactory().setValue(25);
genderGroup.selectToggle(null);
countryCombo.setValue("USA");
sportsCheck.setSelected(false);
readingCheck.setSelected(false);
musicCheck.setSelected(false);
travelCheck.setSelected(false);
commentsArea.clear();
});
Scene scene = new Scene(grid, 500, 450);
primaryStage.setTitle("JavaFX Controls Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
private String getSelectedGender(ToggleGroup group) {
RadioButton selected = (RadioButton) group.getSelectedToggle();
return selected != null ? selected.getText() : "Not specified";
}
private String getSelectedHobbies(CheckBox... checkboxes) {
StringBuilder hobbies = new StringBuilder();
for (CheckBox cb : checkboxes) {
if (cb.isSelected()) {
if (hobbies.length() > 0) hobbies.append(", ");
hobbies.append(cb.getText());
}
}
return hobbies.length() > 0 ? hobbies.toString() : "None";
}
private void handleSubmit(String name, int age, String gender,
String country, String hobbies, String comments) {
String message = String.format(
"Registration Details:\n\nName: %s\nAge: %d\nGender: %s\nCountry: %s\nHobbies: %s\nComments: %s",
name, age, gender, country, hobbies, comments
);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Registration Complete");
alert.setHeaderText("Thank you for registering!");
alert.setContentText(message);
alert.showAndWait();
}
public static void main(String[] args) {
launch(args);
}
}
Layout Managers
Common Layout Examples
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class LayoutDemo extends Application {
@Override
public void start(Stage primaryStage) {
// Create tab pane for different layouts
TabPane tabPane = new TabPane();
// VBox Example
Tab vboxTab = new Tab("VBox");
vboxTab.setClosable(false);
VBox vbox = createVBoxLayout();
vboxTab.setContent(vbox);
// HBox Example
Tab hboxTab = new Tab("HBox");
hboxTab.setClosable(false);
HBox hbox = createHBoxLayout();
hboxTab.setContent(hbox);
// BorderPane Example
Tab borderTab = new Tab("BorderPane");
borderTab.setClosable(false);
BorderPane borderPane = createBorderPaneLayout();
borderTab.setContent(borderPane);
// GridPane Example
Tab gridTab = new Tab("GridPane");
gridTab.setClosable(false);
GridPane gridPane = createGridPaneLayout();
gridTab.setContent(gridPane);
// FlowPane Example
Tab flowTab = new Tab("FlowPane");
flowTab.setClosable(false);
FlowPane flowPane = createFlowPaneLayout();
flowTab.setContent(flowPane);
tabPane.getTabs().addAll(vboxTab, hboxTab, borderTab, gridTab, flowTab);
Scene scene = new Scene(tabPane, 600, 500);
primaryStage.setTitle("JavaFX Layout Managers");
primaryStage.setScene(scene);
primaryStage.show();
}
private VBox createVBoxLayout() {
VBox vbox = new VBox(10); // 10px spacing
vbox.setPadding(new Insets(15));
vbox.setAlignment(Pos.TOP_CENTER);
Label title = new Label("VBox Layout");
title.setStyle("-fx-font-size: 18px; -fx-font-weight: bold;");
for (int i = 1; i <= 5; i++) {
Button btn = new Button("Button " + i);
btn.setMaxWidth(Double.MAX_VALUE); // Expand to full width
vbox.getChildren().add(btn);
}
vbox.getChildren().add(0, title);
return vbox;
}
private HBox createHBoxLayout() {
HBox hbox = new HBox(10); // 10px spacing
hbox.setPadding(new Insets(15));
hbox.setAlignment(Pos.CENTER);
Label title = new Label("HBox Layout: ");
title.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
for (int i = 1; i <= 4; i++) {
Button btn = new Button("Btn " + i);
btn.setPrefWidth(80);
hbox.getChildren().add(btn);
}
hbox.getChildren().add(0, title);
return hbox;
}
private BorderPane createBorderPaneLayout() {
BorderPane borderPane = new BorderPane();
borderPane.setPadding(new Insets(15));
// Top
Label topLabel = new Label("TOP - Header");
topLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold; -fx-padding: 10;");
topLabel.setMaxWidth(Double.MAX_VALUE);
topLabel.setAlignment(Pos.CENTER);
topLabel.setBackground(new Background(new BackgroundFill(Color.LIGHTBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
borderPane.setTop(topLabel);
// Left
VBox leftMenu = new VBox(5);
leftMenu.setPadding(new Insets(10));
leftMenu.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
for (int i = 1; i <= 3; i++) {
Button menuBtn = new Button("Menu " + i);
menuBtn.setPrefWidth(100);
leftMenu.getChildren().add(menuBtn);
}
borderPane.setLeft(leftMenu);
// Center
Label centerLabel = new Label("CENTER - Main Content Area");
centerLabel.setStyle("-fx-font-size: 14px; -fx-padding: 20;");
centerLabel.setAlignment(Pos.CENTER);
borderPane.setCenter(centerLabel);
// Right
Label rightLabel = new Label("RIGHT\nSidebar");
rightLabel.setStyle("-fx-font-size: 12px; -fx-padding: 10;");
rightLabel.setBackground(new Background(new BackgroundFill(Color.LIGHTGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
borderPane.setRight(rightLabel);
// Bottom
Label bottomLabel = new Label("BOTTOM - Footer");
bottomLabel.setStyle("-fx-font-size: 12px; -fx-padding: 5;");
bottomLabel.setMaxWidth(Double.MAX_VALUE);
bottomLabel.setAlignment(Pos.CENTER);
bottomLabel.setBackground(new Background(new BackgroundFill(Color.LIGHTCORAL, CornerRadii.EMPTY, Insets.EMPTY)));
borderPane.setBottom(bottomLabel);
return borderPane;
}
private GridPane createGridPaneLayout() {
GridPane grid = new GridPane();
grid.setPadding(new Insets(15));
grid.setHgap(10);
grid.setVgap(10);
grid.setAlignment(Pos.CENTER);
// Create a 3x3 grid of buttons
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
Button btn = new Button(row + "," + col);
btn.setPrefSize(80, 60);
grid.add(btn, col, row);
}
}
return grid;
}
private FlowPane createFlowPaneLayout() {
FlowPane flowPane = new FlowPane();
flowPane.setPadding(new Insets(15));
flowPane.setHgap(10);
flowPane.setVgap(10);
flowPane.setAlignment(Pos.CENTER);
// Create multiple buttons that will wrap to next line
for (int i = 1; i <= 15; i++) {
Button btn = new Button("Button " + i);
btn.setPrefWidth(100);
flowPane.getChildren().add(btn);
}
return flowPane;
}
public static void main(String[] args) {
launch(args);
}
}
Event Handling
Comprehensive Event Handling Example
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class EventHandlingDemo extends Application {
private ObservableList<Person> people = FXCollections.observableArrayList();
private int personId = 1;
@Override
public void start(Stage primaryStage) {
// Create form components
Label titleLabel = new Label("Event Handling Demo");
titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;");
TextField nameField = new TextField();
nameField.setPromptText("Enter name");
TextField emailField = new TextField();
emailField.setPromptText("Enter email");
Button addButton = new Button("Add Person");
// TableView to display people
TableView<Person> table = new TableView<>();
TableColumn<Person, String> idCol = new TableColumn<>("ID");
idCol.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setCellValueFactory(new PropertyValueFactory<>("email"));
table.getColumns().addAll(idCol, nameCol, emailCol);
table.setItems(people);
// Event Handlers
// Button click event
addButton.setOnAction(e -> {
String name = nameField.getText().trim();
String email = emailField.getText().trim();
if (!name.isEmpty() && !email.isEmpty()) {
people.add(new Person(String.valueOf(personId++), name, email));
nameField.clear();
emailField.clear();
nameField.requestFocus();
}
});
// Key pressed event on name field (Enter to move to email)
nameField.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ENTER) {
emailField.requestFocus();
}
});
// Key pressed event on email field (Enter to add)
emailField.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ENTER) {
addButton.fire();
}
});
// Mouse events on table
table.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) { // Double click
Person selected = table.getSelectionModel().getSelectedItem();
if (selected != null) {
showPersonDetails(selected);
}
}
});
// Context menu for table
ContextMenu contextMenu = new ContextMenu();
MenuItem deleteItem = new MenuItem("Delete");
MenuItem editItem = new MenuItem("Edit");
deleteItem.setOnAction(e -> {
Person selected = table.getSelectionModel().getSelectedItem();
if (selected != null) {
people.remove(selected);
}
});
contextMenu.getItems().addAll(editItem, deleteItem);
table.setContextMenu(contextMenu);
// Drag and drop (simplified)
table.setOnDragDetected(e -> {
Dragboard db = table.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
Person selected = table.getSelectionModel().getSelectedItem();
if (selected != null) {
content.putString(selected.getName());
db.setContent(content);
}
e.consume();
});
// Layout
VBox root = new VBox(15);
root.setPadding(new Insets(20));
root.getChildren().addAll(titleLabel, nameField, emailField, addButton, table);
Scene scene = new Scene(root, 500, 400);
// Global key event (Ctrl+A to select all in table)
scene.setOnKeyPressed(e -> {
if (e.isControlDown() && e.getCode() == KeyCode.A) {
table.getSelectionModel().selectAll();
}
});
primaryStage.setTitle("JavaFX Event Handling");
primaryStage.setScene(scene);
primaryStage.show();
}
private void showPersonDetails(Person person) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Person Details");
alert.setHeaderText("Person Information");
alert.setContentText(String.format("ID: %s\nName: %s\nEmail: %s",
person.getId(), person.getName(), person.getEmail()));
alert.showAndWait();
}
public static void main(String[] args) {
launch(args);
}
// Person class for table data
public static class Person {
private final StringProperty id;
private final StringProperty name;
private final StringProperty email;
public Person(String id, String name, String email) {
this.id = new SimpleStringProperty(id);
this.name = new SimpleStringProperty(name);
this.email = new SimpleStringProperty(email);
}
public String getId() { return id.get(); }
public StringProperty idProperty() { return id; }
public String getName() { return name.get(); }
public StringProperty nameProperty() { return name; }
public String getEmail() { return email.get(); }
public StringProperty emailProperty() { return email; }
}
}
Best Practices for JavaFX Development
- Use MVC Pattern: Separate business logic from UI code
- Leverage Properties and Binding: For reactive UI updates
- Use CSS for Styling: Keep styling separate from logic
- Implement Responsive Design: Use appropriate layouts and constraints
- Handle Exceptions Gracefully: Use dialogs for user feedback
- Optimize Performance: Use background threads for long operations
- Follow JavaFX Conventions: Use the established patterns and idioms
Conclusion
JavaFX provides a comprehensive toolkit for building modern, feature-rich desktop applications in Java. By understanding the core concepts of stages, scenes, nodes, layouts, and event handling, you can create sophisticated GUIs that rival native applications. The combination of powerful controls, flexible layout managers, CSS styling, and robust event handling makes JavaFX an excellent choice for cross-platform desktop development. As you progress, explore advanced features like charts, 3D graphics, multimedia integration, and custom controls to build even more compelling applications.