Lezione 8 · Canale 1 · lunedì 12 ottobre 2026

Interfacce grafiche con Swing

Progettazione del Software

Finestre e componenti

Un'interfaccia grafica Swing è una finestra (JFrame) che contiene componenti: pulsanti, etichette, campi di testo. Il programma costruisce la finestra e poi attende le azioni dell'utente. Il comportamento in risposta a ciascuna azione sta negli ascoltatori.

import javax.swing.JButton;
import javax.swing.JFrame;

public class Principale {
    public static void main(String[] args) {
        JFrame finestra = new JFrame("Prova");
        JButton pulsante = new JButton("OK");
        finestra.add(pulsante);
        finestra.pack();
        finestra.setDefaultCloseOperation(
                JFrame.EXIT_ON_CLOSE);
        finestra.setVisible(true);
        System.out.println("main terminato");
    }
}
  • new JFrame(titolo) crea la finestra, che nasce vuota e invisibile. Si rende visibile con setVisible(true): così si possono creare più finestre e mostrare subito solo la principale.
  • Creare un componente e inserirlo nella finestra sono due operazioni: il pulsante compare solo dopo add.
  • pack() dimensiona la finestra sulle dimensioni preferite dei componenti. Senza, la finestra è troppo piccola e il pulsante non si vede.
  • Per default la X nasconde la finestra e il programma continua a girare. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) lo fa terminare.

I componenti di uso più frequente:

ComponenteUso
JButtonpulsante che l'utente preme
JLabeletichetta che descrive altri elementi, non si preme
JTextFieldtesto su una sola riga
JTextAreaarea rettangolare di testo su più righe

Per un singolo valore si usa JTextField, la cui lunghezza è l'argomento del costruttore (new JTextField(20)). JTextArea vuole righe e colonne: new JTextArea(20, 20) ha 20 righe di 20 caratteri.

Programmazione guidata dagli eventi

Nei programmi sequenziali le istruzioni si eseguono una dopo l'altra. Qui main costruisce l'interfaccia e termina, ma il programma resta in esecuzione finché la finestra è aperta: main terminato compare con la finestra ancora aperta. Gli eventi non sono solo le pressioni dei pulsanti: anche il passaggio da un campo al successivo con Tab ne è uno. main resta semplice, perché la complessità sta negli ascoltatori.

Il pulsante deve ricevere il comportamento da eseguire. Un metodo riceve come argomenti valori (un intero, un booleano) oppure oggetti: il comportamento si passa quindi dentro un oggetto, il cui metodo contiene il codice da eseguire. La classe di questo ascoltatore implementa l'interfaccia ActionListener, che richiede un solo metodo, actionPerformed(ActionEvent e). L'evento, l'oggetto ActionEvent, descrive l'azione e arriva come argomento.

  1. Si definisce la classe che implementa ActionListener, con il codice in actionPerformed.
  2. Si registra un'istanza presso il pulsante: pulsante.addActionListener(ascoltatore). Con un ascoltatore non registrato non succede niente.

Un ascoltatore che modifica la finestra

Esercizio: una finestra con due pulsanti e un'area di testo. Un pulsante aggiunge una riga all'area, l'altro ne cancella il contenuto.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JTextArea;

public class Ascoltatore implements ActionListener {
    private JTextArea testo;

    public Ascoltatore(JTextArea testo) {
        this.testo = testo;
    }

    public void actionPerformed(ActionEvent e) {
        String comando = e.getActionCommand();
        if (comando.equals("Linea"))
            testo.append("una linea\n");
        else if (comando.equals("Cancella"))
            testo.setText("");
    }
}
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;

public class Principale {
    public static void main(String[] args) {
        JFrame finestra = new JFrame("Ascoltatore");
        finestra.setDefaultCloseOperation(
                JFrame.EXIT_ON_CLOSE);

        JButton linea = new JButton("Linea");
        JButton cancella = new JButton("Cancella");
        JTextArea testo = new JTextArea(20, 20);

        Ascoltatore ascoltatore =
                new Ascoltatore(testo);
        linea.addActionListener(ascoltatore);
        cancella.addActionListener(ascoltatore);

        finestra.add(linea, BorderLayout.NORTH);
        finestra.add(cancella, BorderLayout.SOUTH);
        finestra.add(testo, BorderLayout.CENTER);
        finestra.pack();
        finestra.setVisible(true);
    }
}
  • Un ascoltatore per più pulsanti. getActionCommand() restituisce il comando dell'evento, che per un pulsante è il testo scritto sopra. Un solo ascoltatore, registrato su entrambi, li distingue così. Le stringhe si confrontano con equals, non con ==. In alternativa si scrivono due ascoltatori.
  • Condividere l'area. actionPerformed ha l'unico argomento previsto dal sistema, e non se ne possono aggiungere. L'area di testo è però una variabile locale di main, e l'ascoltatore non la vede. Si salva allora un riferimento in un campo dell'ascoltatore, passandolo al costruttore. La variabile di main e il campo testo riferiscono la stessa JTextArea: quella mostrata nella finestra.
  • Modificare il testo. append aggiunge in fondo e non va a capo, come print: il ritorno a capo si scrive nel testo. setText sostituisce tutto il contenuto, e con la stringa vuota lo cancella.

Gli oggetti dell'esercizio

linea : JButtoncancella : JButtonascoltatore : Ascoltatoretesto : JTextArea
Un solo ascoltatore serve entrambi i pulsanti e riferisce l'area di testo mostrata nella finestra.

Due errori tipici: dimenticare addActionListener, e lasciare che l'ascoltatore riferisca un'area diversa da quella mostrata, perché append scrive allora in un'area che nessuno vede. Per accorgersi del primo si comincia actionPerformed con una println: se non compare, il metodo non viene eseguito.

Layout manager

Il layout manager di un contenitore decide dove stanno i componenti e come cambiano le loro dimensioni quando la finestra si ridimensiona. La finestra ha di default un BorderLayout; con setLayout se ne sceglie un altro. I layout dispongono i componenti in posizione relativa, così lo spazio segue il loro ruolo: in un editor la barra del menu resta una striscia e l'area di testo si allarga.

Per provare un layout si usano pulsanti, che mostrano come si allarga ogni zona.

BorderLayout

Il contenitore ha cinque zone. add(componente, posizione) sceglie la zona. Senza posizione il componente va al centro: un secondo add finisce nella stessa zona e si vede solo l'ultimo.

ZonaPosizioneMantieneSi estende
nordBorderLayout.NORTHaltezzain larghezza
sudBorderLayout.SOUTHaltezzain larghezza
ovestBorderLayout.WESTlarghezzain altezza
estBorderLayout.EASTlarghezzain altezza
centroBorderLayout.CENTERnientenello spazio che resta

Una disposizione tipica: in alto il menu, in basso la barra di stato, al centro il contenuto, ai lati i comandi.

import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;

public class Posizioni {
    public static void main(String[] args) {
        JFrame finestra = new JFrame("BorderLayout");
        finestra.setDefaultCloseOperation(
                JFrame.EXIT_ON_CLOSE);
        finestra.add(new JButton("Nord"),
                BorderLayout.NORTH);
        finestra.add(new JButton("Sud"),
                BorderLayout.SOUTH);
        finestra.add(new JButton("Ovest"),
                BorderLayout.WEST);
        finestra.add(new JButton("Est"),
                BorderLayout.EAST);
        finestra.add(new JButton("Centro"),
                BorderLayout.CENTER);
        finestra.pack();
        finestra.setVisible(true);
    }
}

GridLayout

finestra.setLayout(new GridLayout(3, 2)) crea una griglia di 3 righe e 2 colonne. I componenti si aggiungono in sequenza, riga per riga, senza posizione. Le celle hanno tutte la stessa dimensione e riempiono lo spazio, quindi si allargano insieme.

Con righe e colonne entrambe diverse da zero uno dei due numeri è ignorato: conta quello delle righe. Conviene allora indicare solo uno dei due e mettere 0 all'altro. GridLayout(0, 1) è una colonna con tante righe quanti sono i componenti: aggiungerne uno non obbliga a cambiare il layout.

FlowLayout

new FlowLayout() dispone i componenti uno dopo l'altro, centrati, senza allargarli. Se la finestra si restringe, quelli che non entrano passano a una seconda riga, poi a una terza.

Pannelli annidati

Un JPanel è un contenitore con un proprio layout. Si aggiunge a una finestra o a un altro pannello, e può contenere componenti e altri pannelli. Così si combinano layout diversi: la finestra ha il BorderLayout, e in una sua zona sta un pannello con una griglia o con un flusso.

Due errori tipici: creare il pannello e non aggiungerlo alla finestra; aggiungere i componenti alla finestra invece che al pannello. I componenti che devono reagire vanno in variabili, per registrarvi gli ascoltatori: un pulsante creato dentro add basta per vedere la disposizione, ma se ne perde il riferimento.

Prima di scrivere il codice conviene disegnare su carta la gerarchia di contenimento. Per l'esercizio seguente:

  • finestra, con BorderLayout
    • al centro il pannello centro, con GridLayout(0, 1)
      • tre JTextField: primo addendo, secondo addendo, risultato
    • a sud il pannello sud, con FlowLayout
      • il pulsante «Somma»

Esempio: somma di due numeri

Il pulsante somma due interi scritti nei primi due campi e mostra il risultato nel terzo. L'ascoltatore ha bisogno dei tre campi: li riceve dal costruttore, come prima l'area di testo.

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class Somma {
    public static void main(String[] args) {
        JFrame finestra = new JFrame("Somma");
        finestra.setDefaultCloseOperation(
                JFrame.EXIT_ON_CLOSE);

        JTextField primo = new JTextField(20);
        JTextField secondo = new JTextField(20);
        JTextField risultato = new JTextField(20);
        JPanel centro =
                new JPanel(new GridLayout(0, 1));
        centro.add(primo);
        centro.add(secondo);
        centro.add(risultato);

        JButton somma = new JButton("Somma");
        JPanel sud = new JPanel(new FlowLayout());
        sud.add(somma);

        somma.addActionListener(new Addizione(
                primo, secondo, risultato));

        finestra.add(centro, BorderLayout.CENTER);
        finestra.add(sud, BorderLayout.SOUTH);
        finestra.pack();
        finestra.setVisible(true);
    }
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JTextField;

public class Addizione implements ActionListener {
    private JTextField primo, secondo, risultato;

    public Addizione(JTextField primo,
            JTextField secondo,
            JTextField risultato) {
        this.primo = primo;
        this.secondo = secondo;
        this.risultato = risultato;
    }

    public void actionPerformed(ActionEvent e) {
        int a = Integer.parseInt(primo.getText());
        int b = Integer.parseInt(secondo.getText());
        int r = a + b;
        risultato.setText(Integer.toString(r));
    }
}

getText() restituisce una stringa, e le stringhe non si sommano: darebbero la concatenazione. Per questo si convertono in interi, si sommano e si riconverte il risultato in stringa:

r=a+br=a+b

Con a=12a=12 e b=4b=4 il terzo campo mostra 1616.

Se un campo contiene testo non numerico, per esempio 12A, Integer.parseInt lancia NumberFormatException: il metodo si interrompe e il risultato non cambia. L'errore compare solo nella console dell'ambiente di sviluppo, e fuori da esso l'utente non vede nulla. Va invece segnalato nell'interfaccia, con la gestione delle eccezioni.

Formulario

Finestra

  • new JFrame(titolo)
  • nasce invisibile
  • add(componente)
  • pack(): dimensiona
  • setVisible(true)
  • chiusura: EXIT_ON_CLOSE

Layout

  • finestra: BorderLayout
  • setLayout(layout)

BorderLayout

  • nord, sud: altezza fissa
  • ovest, est: larghezza fissa
  • centro: lo spazio che resta

Griglia e flusso

  • GridLayout(righe, colonne)
  • uno dei due a 0: si adatta
  • FlowLayout(): va a capo

Ascoltatore

  • implements ActionListener
  • actionPerformed(e)
  • addActionListener(a)
  • e.getActionCommand()
  • stringhe: equals

Testo

  • append(s): in fondo
  • setText(""): cancella
  • getText(): legge

Somma di due campi

r=a+br=a+b

aa, bb: interi dei campi.