Mi pare che vedere come si può realizzare, avendo scarse conoscenze di un framework e usando la documentazione opportuna, una piccola applicazione stupida possa essere utile a molti italiani che si trovano alle prese con Java a con Swing.
Questo pseudoarticolo tutorial non insegna a programmare: è più un
flusso di coscienza su un processo creativo
, che più che alla
creazione è stato votato alla sperimentazione e apprendimento: che
cosa diavolo è Swing e come ci si possono fare cose? Questa era la mia
domanda (sapevo già la risposta alla prima parte della domanda, ma mi
interessava avere una risposta per la seconda parte).
Proveremo a realizzare una calcolatrice da quattro soldi.
O forse anche più complessa...
Troviamo un aiuto nella documentazione, dalla quale possiamo estrarre la seguente confezione.
import javax.swing.*;
public class Minicalc {
public static void createAndShowGUI() {
// codice creazione GUI e funzionalità ...
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
Al posto del commento mettiamo il codice di creazione. Questo è stato facile; all'inizio ho sperimentato un po' a caso aggiungendo JLabel e JButton... Poi, una volta capiti i meccanismi base, ho scritto il codice per l'interfaccia finale.
La nostra calcolatrice ha bisogno di un display, dieci bottoni per i numeri, un bottone per cancellare, un bottone per chiedere il risultato e quattro bottoni per le operazioni elementari (la nostra calcolatrice non esegue calcoli in virgola mobile, né permette l'inserimento diretto di numeri negativi... ma sono tutte cose che si possono aggiungere dopo).
Abbiamo un totale di sedici bottoni e un campo testo (non editabile). Per il tastierino numerico, il tasto cancella, uguale e le quattro operazioni mi è subito venita in mente una disposizione a griglia. Il che ci porta a GridLayout.
Ma il display non si adattava molto a questo layout, quindi mi serviva un pannello con un layout diverso; poiché il display deve stare sopra il tastierino, spulciando la documentazione sui layout possibili, sono atterrato su BoxLayout, che permette di mettere degli oggetti uno dopo l'altro in un certo orientamento, che io sceglierò verticale.
Ed ora veniamo alla costruzione: il contenitore principale, che è dato dal frame principale e unico che abbiamo (cioè in pratica dalla finestra) dovrà avere il su detto layout BoxLayout e contenere due oggetti: il display e un pannello con layout a griglia. A sua volta questo pannello conterrà i sedici bottoni.
Una volta realizzata l'interfaccia (vedremo dopo alcune note), dobbiamo aggiungergli le funzionalità. Swing ci permette di associare a un bottone un comando identificato da una stringa. Comodissimo, considerando che ogni bottone una un testo diverso che manifesta la sua funzione. Ciò fatto dobbiamo dire però chi è in ascolto degli eventi/azione sui bottoni.
Qui va osservato che sarebbe comodo fare in modo che la classe Minicalc implementi ActionListener (l'ascoltatore) e dunque mettere this come ascoltatore. Purtroppo notiamo subito che non è fattibile, non così direttamente: sia main che createAndShowGUI sono statici, ovvero sono metodi di classe, che esistono anche senza che la classe sia stata istanziata, e in effetti, qui nessuno l'ha istanziata. Tuttavia modificare al volo createAndShowGUI in modo che non sia static, e creare una istanza della classe nel main, porta solo problemi.
Una possibile soluzione è creare una classe sotto
la classe
Minicalc che crei tutto il necessario e implementi ActionListener, e
crearne una istanza in createAndShowGUI. In questo modo
questo piccolo esempio rimarrebbe confinato in un unico file. Ma non è
la soluzione da me scelta: ho optato invece per una classe a
parte. Per inciso, aggiungendo un metodo pubblico per prendere il
numero calcolato, la si potrebbe usare come mini calcolatrice in altre
applicazioni piccole ed inutili.
E dove tutto finisce.
import javax.swing.*;
public class Minicalc {
public static void createAndShowGUI() {
MiniCalculator mc = new MiniCalculator();
mc.calculatorGUI();
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
E dunque ora la nostra attenzione si sposta su MiniCalculator.java.
Dopo gli import di rito, cominciamo la nostra classe così
public class MiniCalculator implements ActionListener {
// il resto...
}
Come variabili private possiamo mettere i vari bottoni, che ci converrà mantenere in un array, i layout, il pannello, il frame (finestra).
Abbiamo ben 16 bottoni funzionali. Ci conviene scrivere 16×4
righe di codice per crearli, impostare l'azione e il listener
e infine aggiungerli al layout?! Ovviamente no. Come detto prima
ciascun bottone ha per etichetta
una stringa diversa, e
possiamo quindi usare come comando per l'azione questa stessa stringa.
Inoltre ricordiamo che il layout scelto è una griglia... Tutto ci
porta a considerare come rapida soluzione la seguente: mettere le
stringhe in un array di stringhe e tramite un for creare, impostare
l'azione e l'ascoltatore. Ovvero qualcosa del tipo
String opsStr[] = {
"1", "2", "3", "+",
"4", "5", "6", "-",
"7", "8", "9", "*",
"C", "0", "=", "/"
};
for(int i=0; i < opsStr.length; i++) {
opsButton[i] = new JButton(opsStr[i]);
opsButton[i].setActionCommand(opsStr[i]);
opsButton[i].addActionListener(this);
}
Non rimane così che creare il display, i pannelli, impostare i layout
e aggiungere, con un altro loop nel caso dei 16 bottoni, gli oggetti
ai rispettivi pannelli
.
Tutta la parte funzionale sarà racchiusa nel metodo actionPerformed(ActionEvent e), che viene chiamato ogni qual volta l'utente preme un bottone. La prima cosa che faremo sarà prendere il comando associato all'azione, che sappiamo essere una stringa corrispondente al testo scritto sul bottone.
Qui si apre un altro dilemma. Per poter fare i nostri conti numericamente, abbiamo bisogno di ricavare il numero come intero, e fin qui niente di complicato. Possiamo per esempio usare una serie di if/else/if e lasciare l'ultimo else a trattare il caso in cui non abbiamo né una operazione, (stringhe +, *, -, / e =) né il tasto C. Tuttavia qui ho optato per un'altra soluzione, tanto per testarne la validità.
Proviamo a convertire la stringa in un numero; se la conversione fallisce, l'utente ha premuto un tasto operazione o Cancella. In pratica usiamo un try/catch.
Questo per quanto riguarda il capire quali tasti sono stati premuti. Ora dobbiamo scrivere il codice per questi tasti e ci ispiriamo vagamente al comportamento normale delle calcolatrici: quando premiamo un tasto numerico, questo equivale ad aggiungere una cifra a destra del numero che abbiamo già. Matematicamente questo vuol dire che
nuovo_numero = vecchio_numero*10 + nuova_cifra;
Quando premiamo C dobbiamo "cancellare" l'ultima cifra, che vuol dire in pratica dividere per 10 il numero che abbiamo (ricordate che la nostra calcolatrice tratta solo interi!).
Quando premiamo un tasto operazione diverso da =, dobbiamo iniziare
un nuovo numero, in modo che le cifre che premeremo poi non cambino
più il precedente. Quando premeremo l'uguale, dovremo considerare
l'operazione che era stata richiesta (e dunque dovremo memorizzarla) e
i due numeri accumulati fino a questo momento e combinarli secondo
l'operaione richiesta.
Invece di premere uguale, l'utente può premere un altro tasto (o lo stesso) operazione e ciò deve essere equivalente alla sequenza = seguita dal tasto operazione successivo. In questo modo è possibile concatenare diverse operazioni senza dover premere ogni volta uguale.
L'idea per implementare ciò è avere due interi (in un array), indicizzati da un indice che all'inizio sarà 0; quando l'utente preme una operazione, dovremo indicizzare il secondo elemento dell'array, incrementeremo l'indice e memorizzeremo l'operazione richiesta. Se l'utente preme = quando l'indice è 0, e ciò vuol dire che c'è solo un numero immesso, non deve accadere nulla ovviamente.
Del resto quando l'indice è 1, vuol dire che l'= può essere premuto (e vuol dire anche che abbiamo memorizzato una precedente operazione), perché abbiamo già inserito il secondo numero, e nello stesso tempo vuol dire che se l'utente preme un'altra operazione invece che =, noi dobbiamo calcolare prima il risultato, metterlo nell'intero indicizzato da 0, memorizzare la nuova operazione e proseguire...
Inoltre, se l'utente preme C quando il secondo numero immesso è 0, cioè ha come risultato quello di riportare nel display il primo numero e la possibilità di modificarlo, cancellandone altre cifre o aggiungendone, e naturalmente scegliendo una nuova operazione.
Tutto questo viene fatto, magari non molto elengatemente, dal seguente codice
public void actionPerformed(ActionEvent e) {
String action = e.getActionCommand();
boolean recet = false;
try {
int num = Integer.parseInt(action);
values[ringindex] = values[ringindex]*10 + num;
} catch (NumberFormatException exc) {
if ( action.equals("C") ) {
if ( values[ringindex] == 0 ) {
values[0] = values[ringindex];
values[1] = 0;
ringindex = 0;
} else {
values[ringindex] /= 10;
}
} else {
boolean askresult = false;
if ( ringindex == 0 ) {
ringindex++;
} else {
askresult = true;
}
if ( action.equals("=") ) {
if ( askresult ) {
values[0] = doops(prevop);
values[1] = 0;
ringindex = 0;
}
} else {
if ( askresult ) {
values[0] = doops(prevop);
values[1] = 0;
ringindex = 1;
recet = true;
}
prevop = action;
}
}
}
if ( !recet ) {
showdisplay(ringindex);
} else { showdisplay(0); }
}
Il booleano recet evita la visualizzazione del nuovo
valore (che è 0) al posto del risultato mantenendo l'indice a 1, in
modo che quando poi l'utente ripreme un numero, compare questo come
fosse nuovo
. Altrimenti, tutto avrebbe funzionato, ma non
avremmo visto il risultato intermedio.
Il metodo doops banalmente è
private int doops(String op) {
if ( op.equals("+") )
return values[0]+values[1];
else if ( op.equals("-") )
return values[0]-values[1];
else if ( op.equals("*") )
return values[0]*values[1];
else if ( op.equals("/") ) {
if ( values[1] != 0 ) {
return values[0]/values[1];
} else {
return 0;
}
}
return 0;
}
Si osservi in particolare la divisione; in realtà potremmo benissimo impostare nel display il testo "Division by 0", azzerare indice operazione precedente, in modo da permettere all'utente di continuare (questo lo lasciamo come esercizio...)
Il metodo showdisplay è molto semplice:
private void showdisplay(int ri) {
display.setText(Integer.toString(values[ri]));
}
A questo punto non rimane che riportare il codice del metodo calculatorGUI
public void calculatorGUI() {
opsButton = new JButton[16];
ringindex = 0;
prevop = "";
calcFrame = new JFrame("Minicalc");
calcFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boxer = new BoxLayout(calcFrame.getContentPane(), BoxLayout.Y_AXIS);
String opsStr[] = {
"1", "2", "3", "+",
"4", "5", "6", "-",
"7", "8", "9", "*",
"C", "0", "=", "/"
};
for(int i=0; i < opsStr.length; i++) {
opsButton[i] = new JButton(opsStr[i]);
opsButton[i].setActionCommand(opsStr[i]);
opsButton[i].addActionListener(this);
}
numGrid = new GridLayout(4, 4, 2, 2);
panel = new JPanel(numGrid);
calcFrame.getContentPane().setLayout(boxer);
for(int i=0; i < opsButton.length; i++ ) {
panel.add(opsButton[i]);
}
display = new JTextField(25);
display.setEditable(false);
calcFrame.getContentPane().add(display);
calcFrame.getContentPane().add(panel);
calcFrame.pack();
calcFrame.setVisible(true);
showdisplay(0);
}
e il costruttore della classe insieme alle variabili private
private JFrame calcFrame;
private BoxLayout boxer;
private JButton opsButton[];
private GridLayout numGrid;
private JPanel panel;
private JTextField display;
private int values[];
private int ringindex;
private String prevop;
public MiniCalculator() {
ringindex = 0;
prevop = "";
values = new int[2];
values[0] = 0;
values[1] = 0;
}
Quelle dell'interfaccia sono create in effetti nell'altro metodo e questo dovrebbe consentire di chiamare più volte con la stessa istanza la calcolatrice (quando la finestra viene chiusa, i vari oggetti collegati tra loro dell'interfaccia potrebbero essere distrutti; ma per esserne certi bisognerebbe leggere meglio la documentazione!)
Quello che non riguarda l'interfaccia viene inizializzato solo nel costruttore e ciò dovrebbe permettere di chiamare di nuovo la calcolatrice mantenendo lo stato precedente alla chiusura (senza distruggere l'istanza)
Ora abbiamo tutto il nostro codice, in due file dai nome, rispettivamente di Minicalc.java e MiniCalculator.java. Per farne un jar la documentazione insegna: prima compiliamo (con javac *.java), poi semplicemente:
jar cf Minicalc.jar *.class
(Posto che siamo nella cartella dove sono solo le nostre classi!). Questo ci porta a un jar non funzionante in automatico, perché java non è in grado di trovare il main: dobbiamo specificare in che classe si trova. Dunque creiamo un altro file, in cui scriveremo
Main-Class: Minicalc
e chiameremo per esempio Manifest.txt. Dunque per ottenere un jar migliore:
jar cfm Minicalc.jar Manifest.txt *.class
Ora, habemus jar! Possiamo, per mettere la ciliegina sulla torta, firmarlo; per far ciò dovremo prima creare una nostra chiave con un certo alias. Poiché questa procedura non è essenziale, qui non la tratto (ripeto però che trovare tutto online).
Per vedere la nostra calcolatrice basterà
java -jar path/al/jar/Minicalc.jar
In Windows, metterete al posto dei /, \; a parte ciò, il resto è invariato.
Avendo già le conoscenze base, una applicazione del genere può uscire fuori in meno di un'ora (diciamo in 30 minuti). Senza avere le conoscenze base di Swing, servirà un po' più (non mi sono cronometrato, ma tra pause occhi, pause acqua, lettura di questa o quella documentazione o esempio con Swing, penso che un paio di orette siano passate)
La minicalcolatrice in questione è ridicola, ma quanto appreso può essere usato senza dubbio per fare una calcolatrice più seria, magari con possibilità di fare grafici... Se dovessi continuare ad esplorare Swing e Java, senza dubbio il mio progetto sarebbe questo... (o forse no)
Intanto per vedere che cose carine e utili si possono fare con Java (Applet in questo caso), eccovi un link interessante:
Mentre il jar bello e funzionante (per quanto inutile!), lo trovate al seguente indirizzo (incorati nello jar, che non è altro che uno zip, troverete anche la cartella src con i sorgenti; non c'è nessuna specifica licenza, ma qui e informalmente dico che sono rilasciati con la GNU General Public Licence v3, la quale affermazione non ha valore legale visto che nei sorgenti non ce ne è nota, ma state certi che se dovessi fare cose più serie ci metterei un bel serio copyleft!!)