Java Advanced GUI Controls

"Real-world GUI applications go beyond buttons and text fields. Swing's advanced controls — radio buttons, checkboxes, and menus — give users richer ways to interact with your program."- Claude 2026

Advanced Controls for GUI Applications

This page introduces more advanced Swing controls that let users make selections and navigate an application:

We'll build these up step by step into a complete Character Builder application where the player chooses a character class, selects abilities, and views a summary through the menu.

Create the GUI Launcher and Class

The class extends JFrame and the constructor handles all setup. The main method is the launcher — it hands off to the Event Dispatch Thread using SwingUtilities.invokeLater().

We'll use setLocationRelativeTo(null) to center the window on screen, and setLayout(null) to position components manually using exact pixel coordinates — giving us precise control over the layout.

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class CharacterBuilder extends JFrame {

    public CharacterBuilder() {
        setTitle("Character Builder");
        setSize(500, 350);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setLocationRelativeTo(null); // center on screen
        setLayout(null);             // manual positioning

        // Components will be added here...

        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new CharacterBuilder());
    }

}
TIP: With setLayout(null) you position every component manually using setBounds(x, y, width, height). This gives you pixel-perfect control but means you're responsible for all spacing. It works well for fixed-size windows like this one.
Add Radio Buttons, CheckBoxes, and Panel to the Form

Radio buttons are grouped using a ButtonGroup so only one can be selected at a time. The buttons themselves are added to a visible JPanel, while the ButtonGroup works invisibly behind the scenes to enforce mutual exclusivity.

Checkboxes are independent — each has its own state and can be selected or deselected without affecting the others.

Example: Radio Buttons for Character Class
// Declare as instance variables
private JRadioButton warriorBtn, mageBtn, archerBtn;
private ButtonGroup classGroup;
private JPanel classPanel;

// Inside the constructor:

// Panel with a titled border to hold the radio buttons
classPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5));
classPanel.setBorder(BorderFactory.createTitledBorder("Choose Your Class"));
classPanel.setBounds(20, 30, 440, 65);

// Create radio buttons
warriorBtn = new JRadioButton("Warrior");
mageBtn    = new JRadioButton("Mage");
archerBtn  = new JRadioButton("Archer");
warriorBtn.setSelected(true); // default selection

// Group them so only one can be selected at a time
classGroup = new ButtonGroup();
classGroup.add(warriorBtn);
classGroup.add(mageBtn);
classGroup.add(archerBtn);

classPanel.add(warriorBtn);
classPanel.add(mageBtn);
classPanel.add(archerBtn);
add(classPanel);
Example: CheckBoxes for Abilities
// Declare as instance variables
private JCheckBox stealthBox, fireballBox, healBox, shieldBox;
private JPanel abilityPanel;

// Inside the constructor:

// GridLayout arranges checkboxes in a 2x2 grid
abilityPanel = new JPanel(new GridLayout(2, 2, 10, 5));
abilityPanel.setBorder(BorderFactory.createTitledBorder("Select Abilities"));
abilityPanel.setBounds(20, 110, 440, 90);

stealthBox  = new JCheckBox("Stealth");
fireballBox = new JCheckBox("Fireball");
healBox     = new JCheckBox("Heal");
shieldBox   = new JCheckBox("Shield Block");

abilityPanel.add(stealthBox);
abilityPanel.add(fireballBox);
abilityPanel.add(healBox);
abilityPanel.add(shieldBox);
add(abilityPanel);
TIP: Use ButtonGroup only for radio buttons — never for checkboxes. Checkboxes are designed to be independent and do not need a group.
Planning and Updating Event Handlers

Before writing event handlers, it helps to plan what each control needs to do. In this application:

A shared updateStatus() method keeps things organized — rather than duplicating logic in every listener, each listener just calls this one method.

// Declare as instance variables
private JLabel statusLabel;
private JButton buildButton;

// Inside the constructor, after adding panels:

statusLabel = new JLabel("Class: Warrior | Abilities: none");
statusLabel.setBounds(20, 215, 440, 25);
add(statusLabel);

buildButton = new JButton("Build Character");
buildButton.setBounds(170, 248, 160, 30);
add(buildButton);

// Attach the same listener to all radio buttons
ActionListener classListener = e -> updateStatus();
warriorBtn.addActionListener(classListener);
mageBtn.addActionListener(classListener);
archerBtn.addActionListener(classListener);

// Attach the same listener to all checkboxes
ActionListener abilityListener = e -> updateStatus();
stealthBox.addActionListener(abilityListener);
fireballBox.addActionListener(abilityListener);
healBox.addActionListener(abilityListener);
shieldBox.addActionListener(abilityListener);

// Build button shows a summary dialog
buildButton.addActionListener(e -> showSummary());
The updateStatus() and showSummary() methods:
private void updateStatus() {
    String charClass = warriorBtn.isSelected() ? "Warrior" :
                       mageBtn.isSelected()    ? "Mage"    : "Archer";

    StringBuilder abilities = new StringBuilder();
    if (stealthBox.isSelected())  abilities.append("Stealth ");
    if (fireballBox.isSelected()) abilities.append("Fireball ");
    if (healBox.isSelected())     abilities.append("Heal ");
    if (shieldBox.isSelected())   abilities.append("Shield Block ");

    String abilityStr = abilities.length() > 0 ? abilities.toString().trim() : "none";
    statusLabel.setText("Class: " + charClass + " | Abilities: " + abilityStr);
}

private void showSummary() {
    String charClass = warriorBtn.isSelected() ? "Warrior" :
                       mageBtn.isSelected()    ? "Mage"    : "Archer";

    StringBuilder abilities = new StringBuilder();
    if (stealthBox.isSelected())  abilities.append("  • Stealth\n");
    if (fireballBox.isSelected()) abilities.append("  • Fireball\n");
    if (healBox.isSelected())     abilities.append("  • Heal\n");
    if (shieldBox.isSelected())   abilities.append("  • Shield Block\n");

    String abilityStr = abilities.length() > 0 ? abilities.toString() : "  • None selected\n";

    JOptionPane.showMessageDialog(this,
        "=== Character Summary ===\n\n" +
        "Class: " + charClass + "\n\n" +
        "Abilities:\n" + abilityStr,
        "Your Character",
        JOptionPane.INFORMATION_MESSAGE
    );
}
Creating, Building, and Activating Menus

A Swing menu bar is built from three components working together:

Once built, the menu bar is attached to the JFrame using setJMenuBar(). Each JMenuItem gets its own ActionListener just like a button.

// Declare as instance variables
private JMenuBar menuBar;
private JMenu fileMenu, helpMenu;
private JMenuItem resetItem, exitItem, aboutItem;

// Inside the constructor:

menuBar = new JMenuBar();

// File menu
fileMenu  = new JMenu("File");
resetItem = new JMenuItem("Reset");
exitItem  = new JMenuItem("Exit");

resetItem.addActionListener(e -> {
    classGroup.clearSelection();
    warriorBtn.setSelected(true);
    stealthBox.setSelected(false);
    fireballBox.setSelected(false);
    healBox.setSelected(false);
    shieldBox.setSelected(false);
    updateStatus();
});

exitItem.addActionListener(e -> System.exit(0));

fileMenu.add(resetItem);
fileMenu.addSeparator();
fileMenu.add(exitItem);

// Help menu
helpMenu  = new JMenu("Help");
aboutItem = new JMenuItem("About");

aboutItem.addActionListener(e ->
    JOptionPane.showMessageDialog(this,
        "Character Builder v1.0",
        "About", JOptionPane.INFORMATION_MESSAGE)
);

helpMenu.add(aboutItem);

menuBar.add(fileMenu);
menuBar.add(helpMenu);
setJMenuBar(menuBar);
Show the JFrame

setVisible(true) should always be the last line of the constructor. This ensures every component is fully added and configured before the window appears on screen. Calling it too early can result in components not showing up or the window rendering before it's fully built.

❌ Too early
setVisible(true);
add(buildButton);  // may not appear!
add(statusLabel);  // may not appear!
✅ Correct
add(buildButton);
add(statusLabel);
setVisible(true);  // everything is ready
Complete Advanced GUI Application
This is the full CharacterBuilder application combining everything from this page — radio buttons, checkboxes, panels, menus, and event handlers — into one runnable file.
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class CharacterBuilder extends JFrame {

    private JRadioButton warriorBtn, mageBtn, archerBtn;
    private ButtonGroup classGroup;
    private JPanel classPanel;

    private JCheckBox stealthBox, fireballBox, healBox, shieldBox;
    private JPanel abilityPanel;

    private JLabel statusLabel;
    private JButton buildButton;

    private JMenuBar menuBar;
    private JMenu fileMenu, helpMenu;
    private JMenuItem resetItem, exitItem, aboutItem;

    public CharacterBuilder() {
        setTitle("Character Builder");
        setSize(500, 310);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setLocationRelativeTo(null);
        setLayout(null);

        // --- Class Panel (Radio Buttons) ---
        classPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5));
        classPanel.setBorder(BorderFactory.createTitledBorder("Choose Your Class"));
        classPanel.setBounds(20, 30, 440, 65);

        warriorBtn = new JRadioButton("Warrior");
        mageBtn    = new JRadioButton("Mage");
        archerBtn  = new JRadioButton("Archer");
        warriorBtn.setSelected(true);

        classGroup = new ButtonGroup();
        classGroup.add(warriorBtn);
        classGroup.add(mageBtn);
        classGroup.add(archerBtn);

        classPanel.add(warriorBtn);
        classPanel.add(mageBtn);
        classPanel.add(archerBtn);
        add(classPanel);

        // --- Ability Panel (CheckBoxes) ---
        abilityPanel = new JPanel(new GridLayout(2, 2, 10, 5));
        abilityPanel.setBorder(BorderFactory.createTitledBorder("Select Abilities"));
        abilityPanel.setBounds(20, 110, 440, 90);

        stealthBox  = new JCheckBox("Stealth");
        fireballBox = new JCheckBox("Fireball");
        healBox     = new JCheckBox("Heal");
        shieldBox   = new JCheckBox("Shield Block");

        abilityPanel.add(stealthBox);
        abilityPanel.add(fireballBox);
        abilityPanel.add(healBox);
        abilityPanel.add(shieldBox);
        add(abilityPanel);

        // --- Status Label ---
        statusLabel = new JLabel("Class: Warrior | Abilities: none");
        statusLabel.setBounds(20, 215, 440, 25);
        add(statusLabel);

        // --- Build Button ---
        buildButton = new JButton("Build Character");
        buildButton.setBounds(170, 248, 160, 30);
        add(buildButton);

        // --- Event Handlers ---
        ActionListener classListener = e -> updateStatus();
        warriorBtn.addActionListener(classListener);
        mageBtn.addActionListener(classListener);
        archerBtn.addActionListener(classListener);

        ActionListener abilityListener = e -> updateStatus();
        stealthBox.addActionListener(abilityListener);
        fireballBox.addActionListener(abilityListener);
        healBox.addActionListener(abilityListener);
        shieldBox.addActionListener(abilityListener);

        buildButton.addActionListener(e -> showSummary());

        // --- Menu Bar ---
        menuBar   = new JMenuBar();
        fileMenu  = new JMenu("File");
        resetItem = new JMenuItem("Reset");
        exitItem  = new JMenuItem("Exit");

        resetItem.addActionListener(e -> {
            classGroup.clearSelection();
            warriorBtn.setSelected(true);
            stealthBox.setSelected(false);
            fireballBox.setSelected(false);
            healBox.setSelected(false);
            shieldBox.setSelected(false);
            updateStatus();
        });
        exitItem.addActionListener(e -> System.exit(0));

        fileMenu.add(resetItem);
        fileMenu.addSeparator();
        fileMenu.add(exitItem);

        helpMenu  = new JMenu("Help");
        aboutItem = new JMenuItem("About");
        aboutItem.addActionListener(e ->
            JOptionPane.showMessageDialog(this,
                "Character Builder v1.0",
                "About", JOptionPane.INFORMATION_MESSAGE));
        helpMenu.add(aboutItem);

        menuBar.add(fileMenu);
        menuBar.add(helpMenu);
        setJMenuBar(menuBar);

        setVisible(true);
    }

    private void updateStatus() {
        String charClass = warriorBtn.isSelected() ? "Warrior" :
                           mageBtn.isSelected()    ? "Mage"    : "Archer";

        StringBuilder abilities = new StringBuilder();
        if (stealthBox.isSelected())  abilities.append("Stealth ");
        if (fireballBox.isSelected()) abilities.append("Fireball ");
        if (healBox.isSelected())     abilities.append("Heal ");
        if (shieldBox.isSelected())   abilities.append("Shield Block ");

        String abilityStr = abilities.length() > 0 ? abilities.toString().trim() : "none";
        statusLabel.setText("Class: " + charClass + " | Abilities: " + abilityStr);
    }

    private void showSummary() {
        String charClass = warriorBtn.isSelected() ? "Warrior" :
                           mageBtn.isSelected()    ? "Mage"    : "Archer";

        StringBuilder abilities = new StringBuilder();
        if (stealthBox.isSelected())  abilities.append("  • Stealth\n");
        if (fireballBox.isSelected()) abilities.append("  • Fireball\n");
        if (healBox.isSelected())     abilities.append("  • Heal\n");
        if (shieldBox.isSelected())   abilities.append("  • Shield Block\n");

        String abilityStr = abilities.length() > 0 ? abilities.toString() : "  • None selected\n";

        JOptionPane.showMessageDialog(this,
            "=== Character Summary ===\n\n" +
            "Class: " + charClass + "\n\n" +
            "Abilities:\n" + abilityStr,
            "Your Character",
            JOptionPane.INFORMATION_MESSAGE);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new CharacterBuilder());
    }

}
Running the Application

Save the file as CharacterBuilder.java, then compile and run from the terminal:

  1. Download and install the Java Development Kit (JDK) if you haven't already.
  2. Compile: javac CharacterBuilder.java
  3. Run: java CharacterBuilder
→ This page was created with help from Gemini and Claude AI.