In questo secondo articolo introdurremo i componenti di base di Swing (container e layout manager, componenti e gestione degli eventi), e vedremo come iniziare a gestire in modo semplice gli eventi associati ad essi
Con questi pochi elementi di base si può realizzare una semplice applicazione Swing didattica, che ci permetta di calcolare: il prezzo di un prodotto applicando l'IVA, e viceversa (dato un prezzo a cui è stata applicata l'IVA estrarre il prezzo base originario).
Container
Come abbiamo visto nel precedente articolo, il JFrame rappresenta la finestra principale. I componenti però non vengono aggiunti direttamente al JFrame ma ad un apposito oggetto Container
, ottenuto attraverso il metodo getContentPane()
:
public class FrameDemo {
public static void main(String[] args) {
JFrame jFrame = new JFrame("Frame with buttons");
jFrame.setSize(400,400);
Container c = jFrame.getContentPane();
jFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
}
Aggiunta di componenti
Un Container è un componente di tipo java.awt.Container
progettato appositamente come contenitore di componenti, che possono essere aggiunti ad esso con un semplice metodo add
. Ad esempio per aggiungere 3 JButton (semplici pulsanti) al Container
public class FrameDemo {
public static void main(String[] args) {
JFrame jFrame = new JFrame("Frame with buttons");
jFrame.setSize(400,400);
Container c = jFrame.getContentPane();
jFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
for(int i=0;i<=2;i++){
c.add(new JButton("Button "+i));
}
}
}
Scelta di un layout manager
Un altro metodo che ha origine nella classe Container è setLayout()
. Questo metodo viene utilizzato per specificare il layout manager, che -come vedremo successivamente- aiuta un Container a posizionare e dimensionare i componenti. Ad esempio potremmo specificare per il nostro Container
un layout manager di tipo FlowLayout, che dispone i componenti uno accanto all'altro:
Container c= jFrame.getContentPane();
c.setLayout(new FlowLayout());
eseguendo il programma visualizziamo:
Dopo questo esempio introduttivo è già più semplice "leggere" l'aspetto che avrà la nostra applicazione alla fine, per riconoscere alcuni elementi di base:
In questa idea generale possiamo notare diverse cose: un'immagine grafica di intestazione con un scritta testuale sottostante, 3 campi di testo descritti dalle label che li precedono e due pulsanti che attiveranno le funzioni dell'applicazione.
Decidiamo di definire un container principale che contiene tutto, con una disposizione dei componenti su 3 righe ed una colonna. Su questo container vogliamo disporre tre container "figli": il primo conterrà l'intestazione, il secondo le label con le aree testuali e l'ultimo i pulsanti. Il container padre ed i primi due container figli organizzeranno i componenti attraverso l'uso di un layout a griglia (GridLayout), mentre il terzo container figlio ri-utilizzerà il FlowLayout, per i JButton.
public class PriceExtractor {
public static void main(String[] args) {
JFrame mainWindow = new JFrame("Price Extractor");
Container mainContainer = mainWindow.getContentPane();
mainContainer.setLayout(new GridLayout(3,1));
// TODO: ADD Container1
// TODO: ADD Container2
// TODO: ADD Container3
mainWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainWindow.setSize(600, 350);
mainWindow.setVisible(true);
}
}
Aggiunta del Layout Manager al JFrame
Il GridLayout ed il FlowLayout fanno parte della famiglia dei Layout Manager, ovvero classi che si occupano di fornire una modalità di disposizione dei componenti all'interno di un container.
FlowLayout
Con il FlowLayout i componenti vengono aggiunti in un flusso ordinato che va da destra a sinistra con un allineamento che va verso l'alto all'interno del container che li ospita.
GridLayout
Nel GridLayout i componenti vengono disposti da sinistra verso destra e dall'alto verso il basso all'interno di una griglia. Tutte le celle della griglia hanno la stessa dimensione, che si preserva anche se il frame viene ridimensionato. I componenti all'interno di esse occuperanno tutto lo spazio possibile.
Aggiungiamo il primo container figlio che include l'immagine di intestazione:
JPanel header = new JPanel();
header.setLayout(new GridLayout(1,1));
Definiamo un componente per l'immagine; per visualizzare l'immagine definiamo una JLabel, utilizziamo il metodo setIcon()
per assegnarle una immagine, e la aggiungiamo al JPanel. Infine il container header viene aggiunto al container padre mainWindow
:
ImageIcon image = new ImageIcon("iva.png");
JLabel banner = new JLabel();
banner.setIcon(image);
header.add(banner);
mainWindow.add(header);
Realizziamo i container figli attraverso la classe java.swing.JPanel
.
La classe JPanel è sottoclasse di java.swing.JComponent
e può essere utilizzata come contenitore di componenti. Nel nostro caso il contenitore JPanel diventa a sua volta un componente aggiunto al suo contenitore padre, ottenuto attraverso il metodo getContentPane()
della classe JFrame, come visto in precedenza.
Possiamo finalmente sostituire al commento relativo al primo container nel mainil codice:
JPanel header = new JPanel();
header.setLayout(new GridLayout(1,1));
ImageIcon image = new ImageIcon("iva.png");
JLabel banner = new JLabel();
banner.setIcon(image);
header.add(banner);
mainWindow.add(header);
Eseguendo l'applicazione vedremo la schermata:
Già in questa prima parte di codice possiamo notare un aspetto interessante, l'uso congiunto di una ImageIcon e di una JLabel per la visualizzazione di un'immagine.
JLabel è una sottoclasse di JComponent, e rappresenta un'etichetta: visualizza solitamente una singola riga di testo di sola lettura ma, come visto, può essere anche utilizzata per la visualizzazione di un immagine. La classe ImageIcon invece permette la visualizzazione di un'immagine GIF
, JPEG
o PNG
.
Nella prossima parte vedremo come rendere finalmente funzionante la nostra micro-applicazione, introducendo una semplice gestione degli eventi
Il JPanel come container
Proseguiamo con il secondo container, aggiungiamo le labels e i campi testuali. Definiamo per il JPanel un layout a 6 righe ed una colonna, 3 righe per le label e 3 per i campi testuali; aggiungiamo infine la JLabel per il campo che contiene il prezzo compreso di IVA:
JPanel body = new JPanel();
body.setLayout(new GridLayout(6, 1));
body.add(new JLabel("Prezzo in euro compreso di IVA (Gli eventuali decimali si separano con il punto)"));
Faremo uso a questo punto di un nuovo componente: JTextField. Si tratta di un area composta da una sola riga in cui l'utente può inserire del testo attraverso la tastiera, oppure in cui è possibile visualizzare del testo. La classe JTextField estende la classe JTextComponent del package java.swing.text
che fornisce molte delle caratteristiche comuni ai componenti Swing basati su testo.
Definiamo quindi una JTextField per il campo del prezzo ivato e aggiungiamola al container body:
JTextField prezzoConIva= new JTextField();
body.add(prezzoConIva);
Un JLabel per indicare il campo che contiene la percentuale IVA:
body.add(new JLabel("IVA %"));
La JTextField che contiene il valore dell'iva da applicare:
JTextField iva = new JTextField("21");
body.add(iva);
Aggiungiamo la JLabel per denotare il campo che contiene il prezzo originario(prezzo di listino) calcolato partendo da quello ivato, e utilizziamo i metodi setOpaque e setBackground della classe JTextField per dare un colore differente al campo di testo:
body.add(new JLabel("Prezzo in euro di listino calcolato"));
JTextField prezzoListino = new JTextField("");
prezzoListino.setOpaque(true);
prezzoListino.setBackground(Color.CYAN);
body.add(prezzoListino);
// aggiungiamo anche questo container alla mainWindow:
mainWindow.add(body);
Il codice che sostituisce il commento // Container2
è quindi:
JPanel body = new JPanel();
body.setLayout(new GridLayout(6, 1));
body.add(new JLabel("Prezzo in euro compreso di IVA (Gli eventuali decimali si separano con il punto)"));
JTextField prezzoConIva= new JTextField();
body.add(prezzoConIva);
body.add(new JLabel("IVA %"));
JTextField iva = new JTextField("21");
body.add(iva);
body.add(new JLabel("Prezzo in euro di listino calcolato"));
JTextField prezzoListino = new JTextField("");
prezzoListino.setOpaque(true);
prezzoListino.setBackground(Color.CYAN);
body.add(prezzoListino);
mainWindow.add(body);
Eseguendo l'applicazione visualizziamo la nostra interfaccia con il secondo container aggiunto:
L'ultimo container da aggiungere è quello indicato con il commento //Container3
all'interno del metodo main
. Concludiamo quindi la nostra interfaccia definendo il JPanel che lo realizza.
In questo caso scegliamo il FlowLayout con allineamento dei componenti centrale, distanza orizzantale fra i componenti di 10 pixel e distanza verticale tra gli stessi pari a 0 pixel:
JPanel footer = new JPanel();
footer.setLayout(new FlowLayout(FlowLayout.CENTER,10,0));
Il JPanel contiene due pulsanti: uno per il calcolo del prezzo di listino partendo dal prezzo ivato e l'altro che realizza invece la funzionalità inversa.
Istanziamo e aggiungiamo il pulsante per il calcolo del prezzo di listino(prezzo base):
JButton calcoloPrezzoBase = new JButton("Calcola");
footer.add(calcoloPrezzoBase);
Un pulsante è un componente su cui l'utente fa clic per lanciare una determinata azione. JButton è la classe che realizza i pulsanti, ed estende javax.swing.AbstractButton
.
Un pulsante di comando genera un ActionEvent (evento associato all'azione da svolgere) quando l'utente fa clic su di esso.
Definiamo poi il pulsante per il calcolo inverso ed aggiungiamolo al JPanel:
JButton calcoloPrezzoIvato = new JButton("Inverso,da prezzo senza iva a prezzo con iva");
footer.add(calcoloPrezzoIvato);
// aggiungiamo l'ultimo JPanel alla mainWindow:
mainWindow.add(footer);
Il commento // Container 3
viene quindi sostituito dal codice:
JPanel footer = new JPanel();
footer.setLayout(new FlowLayout(FlowLayout.CENTER,10,0));
JButton calcoloPrezzoBase = new JButton("Calcola");
footer.add(calcoloPrezzoBase);
JButton calcoloPrezzoIvato = new JButton("Inverso,da prezzo senza iva a prezzo con iva");
footer.add(calcoloPrezzoIvato);
mainWindow.add(footer);
Se eseguiamo il programma vedremo la nostra schermata completa:
Gestione di eventi e azioni
Arrivati a questo punto abbiamo finalmente completato il design dell'unica interfaccia dell'applicazione, e possiamo finalmente renderlo funzionante. Il pulsante "Calcola", una volta premuto, dovrà eseguire la logica per il calcolo del prezzo di listino partendo dal prezzo ivato, il pulsante "Inverso, da prezzo senza iva a prezzo con iva", una volta premuto, dovrà invece prendere il prezzo di listino e calcolare il prezzo finale compreso di IVA.
Le GUI sono guidate dagli eventi, generano eventi quando gli utenti interagiscono con la GUI: la pressione di un pulsante è ad esempio un evento, implementabile tramite due passi:
- Implementare un gestore degli eventi per l'ascoltatore degli eventi da usare
- Registrare l'ascoltatore degli eventi (listener) per il determinato componente
Listener degli eventi
Un listener degli eventi per un evento GUI è un oggetto della classe che implementa una o più interfacce listener degli eventi del package java.awt.event e del package javax.swing.event
. Un listener degli eventi rimane in ascolto di specifici tipi di evento generati dall'oggetto stesso.
Un gestore degli eventi è associabile tramite un un metodo, in risposta ad un particolare tipo di evento: ogni interfaccia listener degli eventi specifica uno o più metodi di gestione che devono poi essere definiti nella classe che la implementa.
ActionListener: un ascoltatore di eventi molto utilizzato
Per i nostri pulsanti abbiamo bisogno di un ascoltare degli eventi che gestisca l'evento di pressione. L'interfaccia che definisce questo tipo di evento è java.awt.event.ActionListener
.
All'interno della classe PriceExtractor definiamo la classe listener che utilizzeremo per il pulsante "Calcola":
private static class ListenerPrezzoListino implements ActionListener{
@Override
public void actionPerformed(ActionEvent event) {
// ...
}
}
Questa classe rappresenta il nostro listener per l'evento di pressione sul pulsante "Calcola", il metodo actionPerformed
invece il gestore dell'evento: tale metodo verrà eseguito ogni volta che si verifica l'evento di pressione sul plusante.
Desideriamo che il gestore recuperi i valori delle 3 aree di testo ed effettui i calcoli necessari per ottenere il prezzo di listino. Progettiamo un costruttore opportuno nel listener per tenere traccia dei riferimenti a questi oggetti:
private static class ListenerPrezzoListino implements ActionListener{
private JTextField prezzoConIva;
private JTextField iva;
private JTextField prezzoListino;
public ListenerPrezzoListino(
JTextField prezzoConIva,
JTextField iva,
JTextField prezzoListino){
this.prezzoConIva= prezzoConIva;
this.iva=iva;
this.prezzoListino=prezzoListino;
}
@Override
public void actionPerformed(ActionEvent event) {
// TODO: logica di calcolo...
}
}
ed infine aggiungiamo nel metodo actionPerformed()
la logica di calcolo:
try{
double prezzo = Double.valueOf(prezzoConIva.getText().trim());
double ivaV = Double.valueOf(iva.getText().trim());
double prezzoDiListino = prezzo/ (1 + (ivaV/100) );
NumberFormat formatter=NumberFormat.getInstance(Locale.US);
formatter.setMaximumFractionDigits(2);
prezzoListino.setText(String.valueOf(formatter.format(prezzoDiListino)));
} catch(Exception e){
JOptionPane.showMessageDialog(null,
"Errore nel calcolo, controlla i dati inseriti",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
Nel codice è stato utilizzato un componente "preconfezionato" molto utile, JOptionPane, per la visualizzazione di finestre di notifica.
Registriamo quindi questo listener sul pulsante "Calcola":
JButton calcoloPrezzoBase = new JButton("Calcola");
// ...
calcoloPrezzoBase.addActionListener(
new ListenerPrezzoListino(prezzoConIva, iva, prezzoListino)
);
dove prezzoConIva
, iva
e prezzoListino
sono le aree di testo definite sempre nel metodo main all'interno del JPanel body
. Possiamo adesso eseguire l'applicazione, inserire un valore nell'area di testo ("Prezzo in euro compreso di IVA..."), ad esempio 50
, e vedere il risultato:
Per l'altro pulsante il discorso è perfettamente identico, definiamo nella classe PriceExtractor
la classe listener:
private static class ListenerPrezzoIvato implements ActionListener{
// ... riferimenti ai componenti
public ListenerPrezzoIvato(...){
// inizializzazione riferimenti ai componenti
}
@Override
public void actionPerformed(ActionEvent event) {
try{
double prezzo = Double.valueOf(prezzoListino.getText().trim());
double ivaV = Double.valueOf(iva.getText().trim());
double prezzoIva = prezzo + ((prezzo/100)*ivaV);
NumberFormat formatter = NumberFormat.getInstance(Locale.US);
formatter.setMaximumFractionDigits(2);
prezzoConIva.setText(String.valueOf(formatter.format(prezzoIva)));
} catch(Exception e) {
JOptionPane.showMessageDialog(null,
"Errore nel calcolo, controlla i dati inseriti",
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}
e la lo registriamo sul secondo pulsante all'interno del metodo main:
JButton calcoloPrezzoIvato = new JButton("Inverso: da prezzo senza iva a prezzo con iva");
// ...
calcoloPrezzoIvato.addActionListener(
new ListenerPrezzoIvato(prezzoConIva, iva, prezzoListino)
);
possiamo adesso eseguire la nostra applicazione finalmente ultimata.
Conclusioni
Abbiamo visto come realizzare una semplice applicazione Swing, introducendo un po' alla volta container e layout manager, componenti e gestione degli eventi.
La struttura dell'applicazione ha avuto un'impostazione didattica: si è preferito realizzare tutto all'interno di una classe per rendere più semplice seguire i concetti esposti, in modo tale che l'attenzione si focalizzasse sulle porzioni di codice fondamentali.
E' chiaro che una buona progettazione e programmazione object oriented impone una realizzazione differente: separare la logica di design da quella di business gestita dagli eventi, collocare ad esempio tutte le classi di design in un package e quelle relative ai listener in un altro, e cosi via... questi aspetti verranno comunque trattati in seguito in articoli via via più specifici.