﻿# GUI programming with Swing

> Swing from the ground up: a window as a tree of panels, layout managers, listeners in four forms and the Event Dispatch Thread – then the calculator and an on-screen keyboard split into a Swing-free model and a view, and the same calculator in MVC.

Source: https://laszlonemes.com/university/programming-technology/swing-gui · Author: László Nemes

Session 5 · Programming Technology

Swing from the ground up: a window as a tree of panels, layout managers, listeners in four forms and the Event Dispatch Thread – then the calculator and an on-screen keyboard split into a Swing-free model and a view, and the same calculator in MVC.

22 min read `Component tree and nested panels` `BorderLayout, FlowLayout, GridLayout, BoxLayout` `Listeners: inner class, lambda, method reference` `Event Dispatch Thread and invokeLater` `Model–view: one refresh() from the model` `Board of buttons for the second assignment`

From this session on, everything we write has a window. The rest of the semester – the second and the third assignment included – uses **Swing**, the GUI toolkit that ships with the JDK. This session covers the three ideas every Swing program is made of: a *tree of components*, a *layout manager* per container that decides where things go, and *listeners* that react when the user does something. Then we look at how to keep the program's rules out of the window, so the GUI stays small and the logic stays testable.

> **The whole session in one breath:** build a tree of panels, give each panel one simple layout, attach a listener to every control, and let each listener do three things only – read the input, call the model, redraw the screen from the model.

After this session you can

- sketch a window as nested panels and turn the sketch into a component tree
- choose between BorderLayout, FlowLayout, GridLayout and BoxLayout for a part of the screen
- write a listener as a named inner class, an anonymous class, a lambda or a method reference
- explain why the GUI is built with invokeLater and why a listener must return quickly
- split a small GUI program into a Swing-free model and a view with one refresh() method
- list the event–listener pairs of your program, the way the assignment documentation expects

## Why Swing

Swing is part of the JDK, so there is nothing to install and every IDE runs it out of the box. The interface is built in code, step by step, with no generated files or hidden magic – which makes it a good tool for learning how GUIs work. It is also not a toy: IntelliJ IDEA, Android Studio and NetBeans itself are Swing applications, and plenty of banking and enterprise desktop software is too.

The more important reason: the concepts carry over. The web, other desktop toolkits and mobile frameworks all use the same three ideas with different names.

| Swing | Web (HTML / JS / React) | Other desktop toolkits |
| --- | --- | --- |
| `JFrame`, `JPanel` (containers) | `<body>`, `<div>` | Window, Stage, Panel |
| `JButton`, `JTextField`, `JLabel` | `<button>`, `<input>`, `<span>` | Button, TextBox / TextField, Label |
| layout manager (`BorderLayout` …) | CSS flexbox / grid | Grid, StackPanel, HBox / VBox |
| `addActionListener(…)` | `addEventListener("click", …)`, `onClick` | Click event, `setOnAction(…)` |
| Event Dispatch Thread (EDT) | the browser’s event loop | the UI thread |

## A window is a tree of components

A Swing interface is a tree. The root is the window, a `JFrame`. Its **content pane** holds panels (`JPanel`), and the panels hold the controls: buttons, text fields, labels. The smallest useful program already shows the pattern – a frame, a panel, two components and a listener:

Hello – the body of main

```java
SwingUtilities.invokeLater(() -> {
    JFrame frame = new JFrame("Hello");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JLabel label = new JLabel("Hello!");
    JButton button = new JButton("Click me");
    button.addActionListener(e -> label.setText("Hello, Swing!"));

    JPanel panel = new JPanel();       // FlowLayout by default
    panel.add(label);
    panel.add(button);
    frame.add(panel);                  // goes into the content pane

    frame.pack();                      // size from the components
    frame.setLocationRelativeTo(null); // centre of the screen
    frame.setVisible(true);            // always last
});
```

The calculator from [Session 1](https://laszlonemes.com/university/programming-technology/oop-recap) comes back this week, rewritten. Its tree has three levels of panels (Fig. 1). The leaves are what the user sees; everything above them exists only to arrange the leaves.

> **Fig. 1** · The calculator's component tree. Containers (blue) only arrange; the leaves are the controls. A component has exactly one parent – adding it somewhere else moves it, it is not copied.
>
> *Diagram:* The calculator window as a tree. The JFrame holds a menu bar and a content pane with BorderLayout. The content pane holds numPanel in CENTER and buttonPanel in SOUTH; numPanel holds labelPanel in WEST and fieldPanel in CENTER. The leaves are the labels, text fields and buttons the user sees. Every component has exactly one parent.

- **component** – Anything visible: JButton, JLabel, JTextField, JToggleButton, JList …
- **container** – A component that holds others: JFrame, JPanel. Every container has a layout manager.
- **content pane** – The part of a JFrame below the menu bar. frame.add(x) actually adds to it.
- **layout manager** – The object that computes the position and size of a container’s children.
- **listener** – An object whose method runs when an event happens, e.g. an ActionListener on a button.

## Layout managers

In Swing you don't give components pixel positions. Each container has a **layout manager** that computes where its children go and how big they are – and computes it again when the window is resized, when the font is larger on another machine, or when a label's text gets longer. The course uses four of them (Fig. 2).

> **Fig. 2** · Four layouts, four behaviours. BorderLayout gives CENTER all the leftover space, FlowLayout wraps, GridLayout makes every cell the same size, BoxLayout keeps each component's own size.
>
> *Diagram:* The four layout managers of the course. BorderLayout has five regions and CENTER gets the leftover space. FlowLayout puts components in a row and wraps when the row is full. GridLayout makes equal cells filled row by row. BoxLayout stacks components in one column or row and keeps their preferred sizes.

| Layout | How it works | Use it for |
| --- | --- | --- |
| `BorderLayout` | five regions: `NORTH`, `SOUTH`, `EAST`, `WEST`, `CENTER`; one component per region | the main split of a window (default of the content pane) |
| `FlowLayout` | left to right in a row, wraps to a new row when full | a row of buttons, a toolbar (default of JPanel) |
| `GridLayout` | `rows × cols` equal cells, filled row by row; `GridLayout(0, 2)` = 2 columns, as many rows as needed | a game board, a keypad |
| `BoxLayout` | a single row or column, components keep their preferred size | forms, vertical lists |

A real screen is never one layout. You get it by **nesting panels**, each with the simplest layout that fits its part. Draw the window first and mark the panels on the drawing, then build the tree from the drawing (Fig. 3).

> **Fig. 3** · The calculator sketched with its panels. The labels and the fields sit in two separate one-column grids – one two-column grid would make the label column as wide as the field column.
>
> *Diagram:* A sketch of the calculator window with its panels marked. The content pane uses BorderLayout: numPanel in CENTER, buttonPanel in SOUTH. Inside numPanel, labelPanel sits in WEST and fieldPanel in CENTER, each a one-column GridLayout, so the labels take only the width they need. The buttons form a one-row GridLayout.

CalcGUI – labels and fields in two columns

```java
JPanel labelPanel = new JPanel(new GridLayout(0, 1, GAP, GAP)); // 0 rows = as many as needed
labelPanel.add(new JLabel("A:"));
labelPanel.add(new JLabel("B:"));
labelPanel.add(new JLabel("Result:"));

JPanel fieldPanel = new JPanel(new GridLayout(0, 1, GAP, GAP));
fieldPanel.add(operand1);
fieldPanel.add(operand2);
fieldPanel.add(result);

JPanel numPanel = new JPanel(new BorderLayout(GAP, GAP));
numPanel.add(labelPanel, BorderLayout.WEST);   // only as wide as the labels
numPanel.add(fieldPanel, BorderLayout.CENTER); // the rest of the width
```

The five operation buttons are not written one by one. The program loops over the `Operation` enum and creates a button per value, each with the same listener class and a different parameter. Adding a new operation is one new line in the enum; the button appears by itself.

CalcGUI – one button per operation

```java
JPanel buttonPanel = new JPanel(new GridLayout(1, 0, GAP, GAP)); // one row
for (Operation operation : Operation.values()) {
    JButton button = new JButton(operation.getSymbol());
    button.addActionListener(new CalcActionListener(operation));
    buttonPanel.add(button);
}
```

Operation.java – symbol + function per constant (outline)

```java
public enum Operation {
    ADD("+", (a, b) -> a + b),
    SUBTRACT("-", (a, b) -> a - b),
    // … MULTIPLY, DIVIDE (throws on b == 0), POWER("^", Math::pow)

    private final String symbol;
    private final DoubleBinaryOperator function;
    // constructor, getSymbol(), apply(a, b) that rejects non-finite results
}
```

Good to know

**Sizing the window**

Call `frame.pack()` and let the window take the size its components ask for. If one component must be a certain size – a board cell, for example – set `setPreferredSize(…)` on that component, not `setSize` on the frame. Avoid `setLayout(null)` with `setBounds(…)`: absolute positions fall apart on a different screen or font size.

## Events and listeners

A GUI program is **event-driven**. `main` builds the window and ends; from then on the program only does something when an event arrives – a click, a key press, a menu choice. You attach a **listener** to each event. For buttons and menu items that is an `ActionListener`, an interface with a single method, `actionPerformed(ActionEvent e)`.

The same listener can be written in four ways, and the QWERTY example uses all four on purpose:

QwertyGUI – the four forms of a listener

```java
// 1. Named inner class – it has a parameter and several buttons share it
class QwertyListener implements ActionListener {
    private final String letter;
    QwertyListener(String letter) { this.letter = letter; }

    @Override
    public void actionPerformed(ActionEvent e) {
        model.type(letter);
        refresh();
    }
}
button.addActionListener(new QwertyListener(letter));

// 2. Anonymous class – the form you will meet in older code
exitItem.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
});

// 3. Lambda – short code used once
clearButton.addActionListener(e -> { model.clear(); refresh(); });

// 4. Method reference – an existing method with the same parameter
backButton.addActionListener(this::backspace);   // void backspace(ActionEvent e)
```

| Form | Pick it when |
| --- | --- |
| named inner class | the listener has a parameter (a letter, an operation) or several controls share it |
| anonymous class | you read older code – before lambdas this was the usual form |
| lambda | the code is short and used once |
| method reference | a method with a matching parameter list already exists |

**What do `->` and `::` mean?** `addActionListener` wants an object with an `actionPerformed` method. Because `ActionListener` has only that one method (it is a *functional interface*), you can give just the method body: `e -> display.setText("")`, parameter on the left, code on the right. A method reference names an existing method instead of writing a body. `this::backspace` does *not* call `backspace` – it hands the method over, to be called later, when the button is pressed. Before the `::` stands whose method it is: `this`, or any other object, e.g. `model::clearHistory`.

An inner class and a lambda can both use the fields of the enclosing object (`model`, `display`), so you don't have to pass everything in as a parameter. A lambda can only use local variables that are *effectively final* – this matters in loops, as the board example below shows.

## One thread for the whole GUI

Swing is **not thread-safe**. Every component must be created and changed on one thread, the **Event Dispatch Thread** (EDT). Listeners run on it automatically. The only place you have to think about it is `main`, which runs on a different thread – so it hands the construction of the GUI over with `SwingUtilities.invokeLater` (Fig. 4).

> **Fig. 4** · The EDT works through the event queue one event at a time. Building the GUI is just the first job in the queue.
>
> *Diagram:* Swing is event-driven. main only hands the GUI construction to the Event Dispatch Thread with invokeLater and ends. Clicks, keys and repaints wait in the event queue; the EDT takes them one at a time and runs the matching listener. While a listener runs, nothing else happens, so a slow listener freezes the window.

The consequence for you: a listener must **return quickly**. While it runs, the window does not repaint and does not react – a `Thread.sleep` or a long loop in a listener freezes the whole window. For something that has to happen repeatedly (a clock, a countdown, an animation later in the semester) use `javax.swing.Timer`, which also runs its code on the EDT:

A Timer instead of sleep

```java
Timer timer = new Timer(1000, e -> secondsLabel.setText(String.valueOf(++seconds)));
timer.start();   // runs every second, on the EDT; timer.stop() ends it
```

## Keep the rules out of the window

A GUI program does three things: it **holds the state and applies the rules**, it **shows** the state, and it **reacts** to the user. If all three live in one class, the class grows fast, every change touches everything, and nothing can be tested without clicking. So we split it. In the **model–view** (MV) structure:

- the **model** is a plain Java class with no Swing in it – `CalcModel` computes and stores the result, `Operation` holds the rules of each operation;
- the **view** builds the window, and its listeners call the model and then redraw the screen from the model with one method, `refresh()`. In MV the view is also the controller.

Follow one click through the calculator (Fig. 5). The listener only *forwards*: it turns text into numbers, lets the model compute, and asks `refresh()` to show the model.

> **Fig. 5** · One click in the MV calculator. The arithmetic happens in the model; the result field is written in exactly one place, refresh(), and always from the model.
>
> *Diagram:* One click in the model–view calculator. The user clicks the multiply button; the listener in CalcGUI parses both text fields into numbers and calls model.calculate. The model stores the result. Then refresh() reads the result back from the model and writes it into the result field. If parsing or the operation fails, the listener clears the result and shows an error dialog.

CalcGUI.CalcActionListener – read, call the model, refresh

```java
@Override
public void actionPerformed(ActionEvent e) {
    try {
        double a = parseOperand(operand1, "A");   // text → number, may throw
        double b = parseOperand(operand2, "B");
        model.calculate(a, operation, b);          // the model computes
        refresh();                                 // the view shows the model
    } catch (NumberFormatException | ArithmeticException ex) {
        model.clearResult();                       // no stale result on screen
        refresh();
        JOptionPane.showMessageDialog(frame, ex.getMessage(), "Error",
                JOptionPane.ERROR_MESSAGE);
    }
}
```

Converting between text and numbers stays in the view: the model works with numbers, the screen works with strings. The parse also guards against input `Double.parseDouble` happily accepts but a calculator should not – `"NaN"` and `"Infinity"` – and accepts a decimal comma as well as a point. Division by zero doesn't throw for `double` (it gives infinity), so `Operation` checks it explicitly and throws an `ArithmeticException` – an exception from the model, reported by the view.

Good to know

**Rule of thumb**

If a piece of code would still be needed after you replaced the whole GUI tomorrow, it belongs in the model.

## The QWERTY keyboard

The exercise of the session is a small on-screen keyboard: a display, the letters `Q W E R T Y`, a `Back` and a `CLR` button. Try it yourself first (Task 1 below); this section explains how the reference solution is structured (Fig. 6).

> **Fig. 6** · The QWERTY program in MV. QwertyGUI holds the components and the listeners; QwertyModel holds the text and the Caps state. The ⊕ marks QwertyListener as an inner class of QwertyGUI.
>
> *Diagram:* Class diagram of the QWERTY program. Qwerty only starts QwertyGUI. QwertyGUI owns a QwertyModel and holds the Swing components. The inner class QwertyListener implements ActionListener and calls model.type for its letter. QwertyModel stores the text and the Caps state and uses no Swing.

- The letter buttons are created in a **loop** from an array and kept in a list, so `refresh()` can relabel them – exactly how a game board is built later.
- The display is read-only (`setEditable(false)`) and not focusable, so key presses reach the buttons.
- The text and the Caps state are **not read back from the components** (`display.getText()`, `capsButton.isSelected()`). The model owns them; the components only show them.

QwertyGUI.refresh() – one method redraws everything

```java
/** The whole screen, always redrawn from the model. */
private void refresh() {
    display.setText(model.getText());
    statusLabel.setText("Characters: " + model.getLength());
    capsButton.setSelected(model.isCapsLock());
    for (int i = 0; i < LETTERS.length; i++) {
        letterButtons.get(i).setText(model.applyCase(LETTERS[i]));
    }
}
```

Because every listener ends with `refresh()`, the display, the counter, the Caps button and the button labels can never disagree. Keeping four components in sync "by hand" in four listeners is where GUI bugs come from.

The solution also listens to the physical keyboard. The recommended way in Swing is a **key binding**: the window's `InputMap` maps a key to a name, the `ActionMap` maps the name to an action, and the action simply clicks the matching button. Unlike a `KeyListener`, this works whichever component has the focus.

QwertyGUI.bindKey – a key presses a button

```java
private void bindKey(String keyStroke, AbstractButton button) {
    JRootPane root = frame.getRootPane();
    root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
            .put(KeyStroke.getKeyStroke(keyStroke), keyStroke);   // key → name
    root.getActionMap().put(keyStroke, new AbstractAction() {     // name → action
        @Override
        public void actionPerformed(ActionEvent e) {
            button.doClick();   // as if the button had been clicked
        }
    });
}
```

| Event | Listener | What it does |
| --- | --- | --- |
| `Q`…`Y` button or key | `QwertyListener(letter)` – inner class | appends the letter in the current case (`model.type`) |
| `Back` button or Backspace | `this::backspace` – method reference | deletes the last character, if any (`model.backspace`) |
| `CLR` button or Esc | lambda | clears the text (`model.clear`) |
| `Caps` toggle | lambda | switches case, the button labels follow (`model.toggleCapsLock`) |
| *QWERTY → Exit* (Ctrl+Q) | anonymous class | exits the program |

In your assignment

**Document your events like this table**

The documentation of the second assignment has to list the *event – listener* pairs: which event of which component runs which listener, and what that listener does. The table above is the format to copy. If a row's "what it does" column needs more than one line, the logic probably belongs in the model.

## The same calculator in MVC

The `calcMVC` example is the calculator rebuilt the way you would structure a larger application, with a history list, a memory (`MC`, `MR`, `M+`, `M−`), errors shown in a status bar instead of a dialog, and JUnit tests for the model. It is a Maven project with three packages – one per layer (Fig. 7). The next session goes deeper into this separation; here it is worth seeing what changes compared to MV.

> **Fig. 7** · Three layers, three packages. The model never calls the view or the controller; it only announces that it changed.
>
> *Diagram:* The calcMVC project in three packages. Calc creates the model, the view and the controller. The controller subscribes to the view's events and calls the model; the model notifies its change listeners without knowing who they are, and the controller then updates the view. The view's only import from the model is the Operation enum, for the button labels.

- **Model** (`calc.model`): the rules and the state – history, memory. When it changes it notifies its subscribers, without knowing who they are.
- **View** (`calc.view`): builds the window and shows only strings it is given. It doesn't hand out its buttons; the controller subscribes through `onXxx(…)` methods, so whether a function is a button, a menu item or a shortcut is the view's own business.
- **Controller** (`calc.controller`): reads text from the view, parses it, calls the model, reports errors, and redraws the view when the model changes.

CalculatorModel – compute, store, notify

```java
private final List<Runnable> listeners = new ArrayList<>();

public void addChangeListener(Runnable listener) {
    listeners.add(listener);
}

public Calculation calculate(double left, Operation operation, double right) {
    double result = operation.apply(left, right);       // the rules live here
    Calculation calculation = new Calculation(left, operation, right, result);
    history.add(0, calculation);
    fireChanged();                                      // "something changed"
    return calculation;
}
```

CalculatorController – wiring both directions

```java
public CalculatorController(CalculatorModel model, CalculatorView view) {
    this.model = model;
    this.view = view;
    view.onOperation(this::calculate);             // user event → controller
    view.onEnterInOperand(() -> calculate(lastOperation));
    view.onClearHistory(model::clearHistory);      // straight to the model
    model.addChangeListener(this::render);         // model changed → view
    render();
}
```

The view's side of `onOperation` shows why `Consumer<Operation>` is the parameter type: every operation button gets the same handler, and the handler is told which operation was chosen.

CalculatorView.onOperation

```java
public void onOperation(Consumer<Operation> handler) {
    operationButtons.forEach((operation, button)
            -> button.addActionListener(e -> handler.accept(operation)));
}
```

| Type | The handed-over code receives | Used for |
| --- | --- | --- |
| `Runnable` | nothing | most buttons and menu items, the model’s change notification |
| `Consumer<Operation>` | an operation | `onOperation`: which operation button was pressed |
| `IntConsumer` | an `int` | `onHistorySelected`: which history entry was chosen |

**Where does the arithmetic go – the controller or the model?** The model. The controller only mediates; the rules (what `mod` means, what division by zero does) are the core of the program. If they were in the controller, only clicking could test them, and a command-line version would have to rewrite them.

CalculatorController.calculate – mediates, doesn't compute

```java
double left = parse(view.getOperandText(Operand.A));               // text → number
double right = parse(view.getOperandText(Operand.B));
Calculation calculation = model.calculate(left, operation, right);  // the model computes
view.showResult(format(calculation.result()));                     // number → text
```

|  | MV (calc, qwerty) | MVC (calcMVC) |
| --- | --- | --- |
| Classes | model + view | model + view + controller |
| Who handles events? | the view | the controller |
| Who updates the screen? | the view, after every event (`refresh()`) | the controller, when the model reports a change |
| When is it the right size? | small programs – plenty for the assignments | larger programs, several views of one model |

## Towards the second assignment

The second assignment is a two-player board game (or a logic game) in Swing. The shared requirements:

- a user-friendly interface and an object-oriented solution – a layered architecture is not mandatory, but separating the model from the view is strongly recommended;
- the board drawn with controls (a grid of buttons) or with graphics;
- the window always fits the board, for every board size;
- a New game menu with the board sizes the task specifies; the program detects the end of the game, announces the winner (or a draw) and starts a new game automatically;
- documentation: task analysis, a UML class diagram of the program, and the event – listener pairs with a short description of each.

The fragments below are building blocks, not solutions. `Board` is your game's model – the rules – and you write it for your own task.

In your assignment

**A board of buttons**

Use a `GridLayout` and create the buttons in a nested loop. Keep them in a `JButton[][]` so `refresh()` can update them, and give each button a listener that knows its own row and column. The loop variables change, so the lambda needs final copies.

BoardGUI – buttons in a loop

```java
boardPanel = new JPanel(new GridLayout(rows, cols));
for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols; col++) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(60, 60));
        final int r = row;   // a lambda may only use effectively final locals
        final int c = col;
        button.addActionListener(e -> fieldClicked(r, c));
        buttons[row][col] = button;
        boardPanel.add(button);
    }
}
```

BoardGUI – the click forwards to the model

```java
private void fieldClicked(int row, int col) {
    // The GUI does not decide whether the move is legal – it asks the model.
    if (board.step(row, col)) {
        refresh();
        checkGameOver();
    }
}
```

In your assignment

**New game with a given size, and a window that follows**

Create the *New game* menu items from a list of sizes, each with a lambda that calls `newGame(rows, cols)`. A new game replaces the board panel; `pack()` resizes the window to the new board. Watch the order: many tasks give the size as columns × rows.

Replacing the board

```java
private void newGame(int rows, int cols) {
    if (boardGUI != null) {
        frame.getContentPane().remove(boardGUI.getBoardPanel());
    }
    boardGUI = new BoardGUI(rows, cols);
    frame.getContentPane().add(boardGUI.getBoardPanel(), BorderLayout.CENTER);
    frame.pack();   // the window follows the new board size
}
```

In your assignment

**Game over, then a new game by itself**

After every move, ask the model whether the game is over. `showMessageDialog` is modal – it returns only after the player clicks OK – so the reset can come right after it. Show whose turn it is, the score or the steps left in a `JLabel` status bar and update it in `refresh()`.

checkGameOver – the end of a round

```java
Player winner = board.getWinner();            // null on a draw
String message = winner == null ? "Draw!" : winner + " won!";
JOptionPane.showMessageDialog(boardPanel, message, "Game over",
        JOptionPane.INFORMATION_MESSAGE);      // modal: waits for OK
board.reset();                                // then a new game starts by itself
refresh();
```

In your assignment

**Select, then move**

Many tasks need two clicks: first one of your own pieces, then the target field or a direction. Store the selection in a field of the view (`null` = nothing selected); the model decides whether the move is legal. If a button's background colour doesn't show, call `setOpaque(true)` and `setBorderPainted(false)` on it.

Two-click moves

```java
private Position selected = null;   // the selected piece, or null

private void fieldClicked(int row, int col) {
    if (selected == null) {
        if (board.isOwnPiece(row, col)) {
            selected = new Position(row, col);
            buttons[row][col].setBackground(Color.YELLOW);
        }
    } else {
        board.move(selected.row(), selected.col(), row, col);  // the model checks
        selected = null;
        refresh();   // also removes the highlight
        checkGameOver();
    }
}
```

The structure to aim for: a **model** (`Board`, `Field`, `Player` …) that holds the board and the rules and imports nothing from Swing, so you can test it with JUnit as in Session 4; and a **view** (`…GUI`, `BoardGUI`) that builds the window, forwards events to the model and redraws the board from it.

## Common mistakes

Common mistake

**setVisible(true) too early**

Components added after the window became visible may not show up. Build everything, call `pack()`, and call `setVisible(true)` last.

Common mistake

**Closing the window doesn't end the program**

By default closing a `JFrame` only hides it and the program keeps running. Set `setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)`.

Common mistake

**Changing components in a visible window**

If you add or remove components at runtime (a new board for a new game), call `revalidate()` and `repaint()` afterwards – or `pack()` if the window size should follow.

Common mistake

**Text is always a String**

`getText()` returns a `String`. Converting it to a number is your job, and so is handling the `NumberFormatException`. Compare strings with `equals`, never with `==`.

Common mistake

**Two controls in one BorderLayout region**

A `BorderLayout` region holds one component; the second one replaces the first. Put a panel in the region and the components in the panel.

Common mistake

**A dialog without a parent**

The first argument of `JOptionPane.showMessageDialog(frame, …)` is the parent window: the dialog appears over it and blocks it while open. Pass your frame (or a component in it), not `null`.

Common mistake

**All the logic in the listeners**

I see this in every semester's second assignment: the win check, the move rules and the score all inside `actionPerformed`. It works until the first change, and it can't be tested. A listener should read, call the model and refresh – if it is longer than that, move the logic into the model.

## Practice

Task 1

**An on-screen QWERTY keyboard**

Build a window with a display (a text field) and the buttons `Q`, `W`, `E`, `R`, `T`, `Y`, `Back` (deletes the last character) and `CLR` (clears the display). Only the buttons may change the display. Add a menu with an *Exit* item, and use MV: the text lives in a model class.

**Approach:**

Content pane with `BorderLayout`: the display in `NORTH`, a `FlowLayout` panel of keys in `CENTER`. Create the letter buttons in a loop over an array; each needs a listener that knows its letter.

```java
private static final String[] LETTERS = {"Q", "W", "E", "R", "T", "Y"};

JPanel keyPanel = new JPanel();          // FlowLayout
for (String letter : LETTERS) {
    JButton button = new JButton(letter);
    button.addActionListener(/* a listener that knows its letter */);
    keyPanel.add(button);
}
```

**The model:**

A `StringBuilder` is the natural field – it appends and deletes at the end cheaply. Every listener changes the model, then calls one `refresh()` that copies `getText()` into the display.

```java
public class QwertyModel {
    private final StringBuilder text = new StringBuilder();

    public void type(String letter) { text.append(letter); }
    public void backspace() { /* delete the last char if there is one */ }
    public String getText() { return text.toString(); }
}
```

Task 2

**Caps and a character counter**

Add a `Caps` toggle button that switches between upper and lower case – the labels of the letter buttons follow – and a status bar in `SOUTH` showing *Characters: n*.

**Approach:**

A `JToggleButton` stays pressed, but don't ask it for its state: store `capsLock` in the model and let `refresh()` set the toggle, the labels and the counter.

```java
capsButton = new JToggleButton("Caps");
capsButton.addActionListener(e -> {
    model.toggleCapsLock();
    refresh();   // refresh() calls capsButton.setSelected(model.isCapsLock())
});
```

Task 3

**Type with the real keyboard**

Make the physical `Q`…`Y`, Backspace and Esc keys work as if the matching button had been clicked.

**Approach:**

Use key bindings on the root pane with `WHEN_IN_FOCUSED_WINDOW`, and let the action call the button's `doClick()` – that way the key and the button share one listener. Key names for `KeyStroke.getKeyStroke` are `"Q"`, `"BACK_SPACE"`, `"ESCAPE"`. Make the display non-focusable so it doesn't swallow the keys.

Task 4

**A full keyboard with a length limit**

Extend the keyboard to three rows (`QWERTYUIOP`, `ASDFGHJKL`, `ZXCVBNM`), add `Space` and an `Enter` that shows the typed text in a `JOptionPane`. At most 30 characters fit on the display: when it is full, the letter buttons are disabled.

**The rows:**

One panel per row, and the row panels stacked in a one-column grid.

```java
String[] rows = {"QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"};
JPanel keyboard = new JPanel(new GridLayout(0, 1));   // one row per panel
for (String row : rows) {
    JPanel rowPanel = new JPanel();
    // for each char of row: a button, added to rowPanel
    keyboard.add(rowPanel);
}
```

**The limit:**

The limit is a rule, so the model enforces it (`type` does nothing when full). The view only reflects it in `refresh()`.

```java
// in refresh(): the model decides, the view only reflects it
boolean full = model.getLength() >= QwertyModel.MAX_LENGTH;
for (JButton button : letterButtons) {
    button.setEnabled(!full);
}
```

Task 5

**A new operation for the calculator**

Add a remainder operation (`mod`) to the MV calculator. You should not have to touch `CalcGUI` at all – if you do, ask why.

**Approach:**

The buttons are generated from `Operation.values()`, so one new enum constant is the whole change. Think about which input must be rejected, as `DIVIDE` does.

```java
MODULO("mod", (a, b) -> {
    // which check does DIVIDE already do that this one needs too?
    return a % b;
}),
```

Task 6

**A counter, twice**

Write a window with a label showing a number, a `+1` and a `Reset` button. First in MV (the view calls the model, then `refresh()`), then in MVC (the model notifies change listeners, a controller wires the view's buttons to the model). Compare which class changed.

**The MVC version:**

The model keeps a `List<Runnable>` of listeners and calls them after `increment` and `reset`. The view doesn't know the model; the controller connects both directions.

```java
// the view only exposes what the controller needs
public void showValue(int value) { valueLabel.setText("Value: " + value); }
public JButton getIncButton() { return incButton; }

// the controller connects both directions
view.getIncButton().addActionListener(e -> model.increment());
model.addChangeListener(() -> view.showValue(model.getValue()));
```

For more detail on layouts and listeners, the Oracle tutorial [Creating a GUI With Swing](https://docs.oracle.com/javase/tutorial/uiswing/) is still the best reference – in particular *Laying Out Components Within a Container* and *Writing Event Listeners*.

## Summary

- A Swing window is a tree: JFrame → content pane → panels → controls. Every component has exactly one parent.
- Each container has one layout manager. Complex screens come from nesting panels with simple layouts – sketch the screen first.
- BorderLayout for the main split, FlowLayout for rows of buttons, GridLayout for boards and keypads, BoxLayout for forms.
- Listeners can be named inner classes, anonymous classes, lambdas or method references; this::method hands a method over, it doesn’t call it.
- Build the GUI with SwingUtilities.invokeLater and keep listeners fast – they run on the one Event Dispatch Thread.
- Keep the rules in a Swing-free model. A listener reads input, calls the model and calls refresh(); refresh() redraws everything from the model.
- MV is plenty for the assignments; MVC adds a controller and a model that notifies its listeners.
