Fondamenti di programmazione Java, lezione 3.

Fondamenti di programmazione Java,
lezione 3.
Buoncompagni Luca
30 gennaio 2014
2
Indice
Indice
3
4 Interfacce Grafiche
4.1 Benefici della Virtualizzazione della JM . . . . . . . . . . . . . .
4.2 Swing & awt . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Esercizio 4.0: Creazione di una GUI con WindowsBuildePro . . .
4.4 Esercizio 4.1: Struttura delle classi di una GUI . . . . . . . . . .
4.5 Esercizio 4.2: Creare un file cliccabile per lanciare un programma
3
5
5
6
8
10
15
4
INDICE
Capitolo 4
Interfacce Grafiche
4.1
Benefici della Virtualizzazione della JM
Una delle caratteristiche più interessanti di Java è quella di permettere ad un
qualsiasi programma di funzionare indipendentemente dal basso livello del computer che si sta usando, come ad esempio il sistema operativo. Per proporre un
altro esempio, considerate inoltre che ogni programma scritto in Java è anche in
grado di funzionare all’interno di una pagina web. Tuttavia si deve considerare
che a volte possono nascere incongruenze tra i diversi sistemi operativi, come
ad esempio diversi simboli per descrivere il percorso delle directory o una nuova
linea in un file. Per ovviare a questi problemi è buona norma quella di riferire, a
qualsiasi informazione, che dipende dal sistema operativo attraverso il comando:
1
System.getProperty( proprty);
dove property è una stringa che può assumere questi valori1 . Durante l’esecuzione del programma questo comando richiede al sistema operativo che viene
usato di fornirgli l’informazione indicata.
Questo breve preambolo intende giustificare il perché si dovrebbe scegliere
di utilizzare Java per la creazione di GUI (Graphical User Interface). Il motivo
è che così facendo si può creare un’applicazione in grado di essere usata sulla maggior parte dei dispositivi, anche attraverso la rete. In particolare, nelle
sezioni successive si proporrà una rassegna molto veloce delle capacità date dalle
API swing e awt2 fornite di default da Java. Queste lavorano in collaborazione
tra di loro permettendo la creazione di interfacce grafiche. Tuttavia è bene ricordare che, in linea generale, non è mai consigliato usare nello stesso progetto
due librerie diverse che implementano lo stesso tipo di oggetti. Inoltre, un’altra
caratteristica molto importante che la macchina virtuale Java propone è la possibilità di usare complessi meccanismi Real-Time in modo automatico (senza il
bisogno di nessun comando). Infatti Java risolve autonomamente problemi, per
niente banali, come ad esempio quello di sincronizzare il programma rispetto
ad un input proveniente dall’utente. Tuttavia questo rimane vero fino a che si
considerano GUI realizzate da un’unica finestra e che non debbano fare calcoli
che richiedano troppo tempo.
5
6
CAPITOLO 4. INTERFACCE GRAFICHE
4.2
Swing & awt
Il numero delle classi e oggetti grafici che queste librerie mettono a disposizione è
molto alto e non avremo modo di affrontarle tutte durante questo elaborato (una
lista abbastanza completa si può trovare qui3 ). Queste classi implementano
oggetti che possono essere visualizzati a schermo, questi possono interagire tra
di loro e con input da tastiera o da mouse. Il comportamento dei diversi tipi
di componenti che formano una GUI sono stati standardizzati e ben conosciuti
dall’utente, anche se non abbiamo mai usato il loro nome; una lista dei diversi
componenti la si può consultare qui4 . Infine si proporre anche la completa e
interattiva descrizione di queste librerie fornita da oracle5 .
Tra gli elementi principali di una interfaccia, è sicuramente presente la finestra implementata dall’oggetto JFrame; per crearne una si consiglia di scrivere:
1
2
import java.awt.BorderLayout;
import java.awt.EventQueue;
3
4
5
6
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
7
8
9
public class MyFrame extends JFrame {
10
private JPanel contentPane;
11
12
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
// lancia un thread indipendente che gestisce la GUI
public void run() {
try {
MyFrame frame = new MyFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// costruttore
public MyFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
27
28
29
30
31
32
33
34
35
36
37
}
In questa classe è presenta il metodo main da cui parte l’esecuzione del pro-
4.2. SWING & AWT
7
gramma. In questo caso, nel main viene implementato un metodo che permette
di far partire un programma completamente indipendente dal primo. Infatti
il programma che parte dal main finisce subito, mentre la GUI rimane aperta.
Questo meccanismo viene utilizzato per non doversi preoccupare delle relazioni
temporali e di sincronizzazione tra i diversi oggetti ed è estremamente consigliato
di averne uno solo per GUI.
Un altro tipo di elemento che analizzeremo è il JPanel, o l’analogo JScrollPane. Questo implementa un pannello dentro al quale è possibile inserire altri
oggetti dell’interfaccia. Il metodo con cui vengono inseriti oggetti è simile a
quello con cui si gestiscono le strutture di dati composti: List e Set. Infatti
basterà usare:
1
2
3
JPanel panel = new JPanel();
JButton button = new JButton();
panel.add( button);
per aggiungere un bottone ad un pannello ad esempio. La posizione in cui tale
bottone viene messo all’interno del panello è definita dalla proprietà layout del
pannello stesso. Esempi di layaout disponibili sono consultabili a questo link6 .
Ricordate che è sempre possibile aggiungere un pannello ad un altro anche con
layout diversi, questa struttura gerarchica permette di posizionare oggetti con
molta flessibilità.
Un caratteristica di tutti gli elementi di un’interfaccia è quella di poter reagire a degli input dati dagli utenti. Per farlo c’è bisogno di quello che viene
comunemente chiamato listener, cioè un programma che, ciclando su se stesso,
rimane in ascolto di un particolare evento legato ad un oggetto e, eventualmente,
ne notifica l’occorrenza. Ad esempio, sarà possibile inserire il seguente codice
all’interno di un metodo:
1
2
3
4
5
6
7
8
9
10
11
....
JButton btn = new JButton( "click here");
contentPane.add(btn, BorderLayout.SOUTH);
btn.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
// scrivere qui cosa fare quando il bottone viene premuto
.....
}
});
....
che permette di scrivere il codice da considerare nel momento in cui il mouse è
stato rilasciato sopra il pulsante. Esistono molti altri tipi di listeners come descritto in questa lista7 . Da notare che nell’esempio precedente compare l’utilizzo
della parola chiave Override. Infatti, come nell’esempio del capitolo precedente,
tutta questa API si basa su meccanismi polimorfi. Questo permette di riuscire
a fare cose di una certa difficoltà scrivendo poche righe di programmazione, ma
d’altro canto richiede una profonda conoscenza del funzionamento di ogni classe.
Un aiuto decisamente interessate viene proposto dal plug-in per Eclipse Windows Builder Pro8 che permette di usare un intuitivo meccanismo di drag &
drop per generare automaticamente linee di codice simili a quelle che abbiamo
8
CAPITOLO 4. INTERFACCE GRAFICHE
visto in precedenza. Tuttavia ha lo svantaggio di fornire codici difficilmente
interpretabile e, inoltre, di difficile manutenzione. Il resto di questo capitolo si
baserà sull’utilizzo di questo software per generare la base dell’interfaccia in modo semplice. Dopo di che, il codice verrà modificato manualmente in modo che
il programma esegui le operazioni volute. In particolare, vedremo come riuscire
ad utilizzare queste librerie per fare un semplice questionario.
4.3
Esercizio 4.0: Creazione di una GUI con
WindowsBuildePro
In figura 4.3 è presente l’organizzazione degli oggetti utilizzati per generare
questo semplice questionario. Vediamo come è possibile crearlo utilizzando il
plug-in descritto sopra. Per prima cosa creare un nuovo progetto e dopo fate
click sulla piccola freccia che c’è a sinistra della seconda icona a partire da
sinistra della barra più in alto della finestra di Eclipse. Notate che questa icona
non è presente se non si è istallato il plug-in. Dopo di che andate su:
swing
JFrame
si aprirà una finestra in cui si richiede di mettere il nome della classe che stiamo
per creare, ad esempio: GuiSample, posizionata nella source folder src del vostro
progetto. Così facendo si apre la classe che, a differenza delle altre volte, presenta
due tipi di presentazioni. Infatti, potete notare che in basso c’è la presenza delle
linguette che aprono due schede diverse; denominate Source e Design. La prima
contiene il file testuale mentre la seconda la visualizzazione grafica di quello che
implementa la prima. I due file dovrebbero rimanere sempre sincronizzati tra
loro, ed è per questo che a volte è necessario fare une reparsing tramite il terzo
Figura 4.1: fig:esempio di un’interfaccia, organizzazione degli oggetti
4.3. ESERCIZIO 4.0: CREAZIONE DI UNA GUI CON WINDOWSBUILDEPRO9
pulsante della barra in alto, presente nella scheda di design. Così facendo è
possibile creare oggetti tramite il mouse e vedere come questi vengono mappati
all’interno del codice. Inoltre è possibile usare il comando di run per vedere
la finestra nella sua vera forma. All’interno della scheda di design notate che
è presenta l’albero degli oggetti aggiunti alla finestra, in alto a sinistra. Le
proprietà dell’oggetto selezionato, come ad esempio il nome e l’inizializzazione,
la si può modificare dal riquadro in basso a sinistra. Infine è presente una lista
dei possibili oggetti che possono essere aggiunti, nel centro.
Come esercizio cercate di riprodurre la finestra di figura seguendo queste
operazioni nell’ordine indicato:
1. JFrame e contentPanel: sono aggiunti di default durante la creazione della
classe, questo perché abbiamo scelto di creare una finestra. Di default
questi elementi sono configurati in modo da aver il layout: BorderLayout.
2. JLabel: è aggiunto al contentPane nella sezione North del layout. Inserite
qui un titolo o fate in modo che, nella sezione Source del plug-in, sia pari
al valore di una costante definita.
3. JButton: è aggiunto nella sezione South del content-panel. Una volta
aggiunto cliccare con il tasto destro sopra al bottone. Qui sono presenti
alcune proprietà, tra cui Add event handle. Da qui potete scegliere quale
listener collegare a questo oggetto, scegliete: mouse
mouseReleased.
4. JScrollPane: è aggiunto nella parte centrale del contentPanel. Notate che
aggiungendolo si perde la possibilità di usare la parte est e ovest. Se non
si fossero fatti i primi due punti avremmo perso anche la possibilità di
usare quella nord e sud.
5. JPanel: è aggiunto all’interno di JScrollPane e permette di potervi inserire più oggetti diversi. Configurate il layout di questa componente
come BoxLayout, e nelle sue proprietà: layout
constructor configurate
Y_AXIS. Quest’ultima proprietà farà in modo che tutti gli oggetti inseriti
successivamente su questo pannello saranno messi uno sotto l’altro.
Facendo così si è creata la parte base del questionario. Per il concetto di modularità visto nel primo capitolo, risulta inefficiente mettere su questa classe anche
la descrizione di tutte le domande. Infatti, è più comodo descrivere un’unica
domanda generica, e poi usarla tutte le volte che è necessario. Per farlo cliccate nuovamente nella seconda icona in alto, come avete fatto per creare la
precedente finestra; ma questa volta digitate:
Swing
JPanel
e assegnategli un nome, ad esempio: Question. Questa classe servirà per estendere un generico JPanel in modo da definire un determinato tipo di pannello
che si vuole usare per visualizzare ogni singola domanda. Una volta posizionati
sulla scheda Design di questo nuovo pannello configurate il suo layout come
BorderLayout e seguite i punti:
6. JPanel: è aggiunto sulla parte nord nel pannello Question. Settate il suo
layout come: FlowLayout, così tutti i prossimi oggetti che verranno inseriti
saranno messi uno accanto all’altro.
10
CAPITOLO 4. INTERFACCE GRAFICHE
7. JLabel: è aggiunto al precedente pannello e conterrà il numero della
domanda.
8. JLabel: è ancora aggiunto al pannello precedente e conterrà il testo della
domanda.
9. JPanel: è aggiunto sulla parte centrale del pannello Question
10. JRadioButton: è aggiunto sul pannello del punto precedente. Aggiungetene
solo uno così da generare il codice adeguato, dopo dovremmo cambiarlo/copiarlo per fare in modo che si comporti come desiderato.
Per effettuare l’ultima parte dell’ultimo punto spostatevi sulla scheda Source
del plag-in. Da qui si vede il codice che genererà il pannello Question come
desiderato. Da notare che la classe extende JPanel, quindi erediterà tutte le
sue funzionalità. A questo punto aggiungete tra gli attributi della classe questa
variabile:
1
private ButtonGroup answerGroup = new ButtonGroup();
che descrive un oggetto in grado di raccogliere tutti i JRadioButton di una domanda in modo esclusivo, cioè non sarà possibile selezionare più di una risposta.
Ora cercate il codice generato da eclipse per aggiungere un unico JRadioButton
e sostituitelo con:
1
2
3
4
5
6
answerGroup = new ButtonGroup();
for( .... ){ // per tutte le possibili risposte s
JRadioButton rdbtnNewRadioButton = new JRadioButton( s);
answerGroup.add( rdbtnNewRadioButton);
panel_1.add( rdbtnNewRadioButton);
}
dove panel_1 è la variabile che indica il pannello inserito al punto 9. Qui dovrete
implementare la logica del ciclo for in un contesto più ampio che affronteremo
nella prossima sezione.
4.4
Esercizio 4.1: Struttura delle classi di una
GUI
Ora che è stata implementata la parte base dell’interfaccia grafica, vediamo come
si può strutturare il flusso dei dati tra le due classi in modo da visualizzare un
semplice questionario. L’idea è quella di creare una terza classe in grado di
descrivere pienamente una generica domanda. Ad esempio la definizione dei
suoi metodi e attributi potrebbe essere:
1
public class QuestionFactory {
2
3
4
5
6
7
// contiene testo della domanda
private String question;
// contiene numero della domanda
private Integer questionNumber;
// contiene lista di tutti i testi delle risposte
4.4. ESERCIZIO 4.1: STRUTTURA DELLE CLASSI DI UNA GUI
11
private List< String> answers = new ArrayList< String>();
// per ogni testo della risposta contiene true se corretto,
false altrimenti
private List< Boolean> isCorrect = new ArrayList< Boolean>();
8
9
10
11
// costruttore, inizializza il testo e il numero della domanda
public QuestionFactory( Integer questionNumber, String
question){...}
12
13
14
// aggiungi una risposta e se e’ corretta o meno
public void addAnswers( String answer, Boolean isCorrect){...}
15
16
17
// ritorna il numero della domanda
public Integer getQuestionNumber(){...}
18
19
20
// ritorna il testo della domanda
public String getQuestion(){...}
21
22
23
// ritorna la risposta numero idx
public String getAnswer( Integer idx){...}
24
25
26
// ritorna tutte le risposte
public List< String> getAnswer(){...}
27
28
29
// ritorna true se la risposta con indice idx e’ corretta
public Boolean getIsCorrect( Integer idx){...}
30
31
32
// ritorna il numero delle risposte
public Integer getNumberOfAnswers(){...}
33
34
35
// ritorna se la risposta ’’answer’’ e’ corretta o meno
public Boolean isCorrectAnswer( String answer){...}
36
37
38
}
Quello che vi viene chiesto di fare in questo esercizio è: implementare la classe sopra indicata. Stanziarla per tutte le domande volute all’inizio del metodo main
e visualizzarla nella finestra principale al termine del costruttore della classe
GuiSample. Per fare questo ultimo punto riferitevi all’esempio precedente in cui
un bottone veniva aggiunto ad un pannello; in questo caso dovrete aggiungere
un pannello ad un pannello di tipo Question. Se provate a lanciare il programma a questo punto dell’implementazione dovreste essere in grado di vedere il
questionario visualizzato ma senza nessuna azione da parte del pulsante. Per
concludere implementate il listener del pulsante in modo tale che:
• Se il questionario non è completato quando si preme il pulsante il programma restituisce un’errore
• Altrimenti, il programma restituisce un messaggio in cui si notifica il
numero totale delle risposte giuste e l’indice della domanda per quelle
sbagliate.
Un modo elegante per far visualizzare un’errore, o un messaggio, attraverso una
finestra pop-up è:
12
1
2
3
4
5
6
7
8
CAPITOLO 4. INTERFACCE GRAFICHE
// visualizza una finestra (pop-up) per le informazioni.
// Opzioni accettate:
// int option = JOptionPane.ERROR_MESSAGE;
// int option = JOptionPane.INFORMATION_MESSAGE;
private static void displayPanel( String info, String title, int option){
JOptionPane popUp = new JOptionPane();
a.showMessageDialog( popUp, info, title, option);
}
Vediamo ora, per punti, come modificare il codice in modo da ottenere il
comportamento voluto. Un modo semplice di descrivere l’intero passaggio dei
dati tra le tre classi può essere il seguente:
GuiSample
classe
• aggiungi un attributo che servirà per tenere in memoria tutte le istanze
della classe Question che verranno create in seguito.
1
2
// lista di tutte le domande nel test
private final List< Question> questionList = new ArrayList<
Question>();
GuiSample.main()
lanciatore del programma
• Create tutte le domande (new QuestionFactory( ...)) e salvate gli oggetti
relativi in un array.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// crea un array di domande
List< QuestionFactory> questions = new ArrayList<
QuestionFactory>();
// crea la prima domanda
QuestionFactory q1 = new QuestionFactory( 1, " questa e’ la prima
domanda?");
// aggiungi le risposte
q1.addAnswers( "si.", true);
q1.addAnswers( "no", false);
q1.addAnswers( "bho", false);
// aggiungi la prima domanda e le sue risposte al array
questions.add( q1);
// crea la seconda domanda
QuestionFactory q2 = new QuestionFactory( 2, " Mentre questa e’ la
prima domanda?");
// aggiungi le risposte
q2.addAnswers( "si.", false);
q2.addAnswers( "no", true);
// aggiungi la seconda domanda e le sue risposte al array
questions.add( q2);
• Il programma creerà una nuova GuiSample (utilizzando il codice auto
generato da WindowsBuilderPro) ma in aggiunta c’è bisogno di fornire
(come parametro d’ingresso al costruttore) tutte le domande inizializzate
al punto precedente, contenute nella variabile questions.
4.4. ESERCIZIO 4.1: STRUTTURA DELLE CLASSI DI UNA GUI
GuiSample.GuiSample( List< QuestionFactory> questions)
13
constructor
• La finestra verrà creata utilizzando il codice auto generato dal plug-in di
eclipse.
• Aggiungete tutte le domande alla finestra (new Question()) utilizzando i
valori della QuestionFactory stanziata in precedenza.
1
2
3
4
5
6
7
8
9
// per tutte le domande passate al costruttore della finestra
for( QuestionFactory q : questions){
// crea un nuovo pannello che contiene la domanda
Question newQuestion = new Question( q);
// aggiungi la domanda al pannello creato al punto 9
panel_1.add( newQuestion);
// aggiungi la domanda all’attributo della classe
questionList.add( newQuestion);
}
Question
class
• aggiungete gli attributi necessari per tenere in memoria sia il gruppo di
JRadioButton di ogni domanda, che la classe della domanda.
1
2
private ButtonGroup answerGroup = new ButtonGroup();
private QuestionFactory question;
Question.Question()
constructor
• Il programma mostrerà la domanda e le risposte (usando il codice generato
e modificato come indicato nella sezione precedente).
• Implementate il metodo getAnswersCorrecteness() che ritorni true o false
in base alla risposta giusta o sbagliata rispettivamente. Se non viene data
nessuna risposta questo metodo ritornerà null.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ottieni la risposta selezionata e controlla se e’ corretta
public Boolean getAnswersCorrecteness(){
// ottieni un elemento in grado di ciclare su tutti i
RadioButton
Enumeration<AbstractButton> allRadioButton =
answerGroup.getElements();
String answer = null;
// per tutti gli elementi dentro al RadioGroup
while(allRadioButton.hasMoreElements()){
// recupera un RadioButton
JRadioButton temp= (JRadioButton)
allRadioButton.nextElement();
// controlla se e’ selezionato
if( temp.isSelected()){
// ottieni testo della selezione
answare = temp.getText();
// esci (solo una puo’ essere selezionata)
break;
}
14
CAPITOLO 4. INTERFACCE GRAFICHE
}
// se c’e’ stata una risposta
if( answer != null)
// controlla che sia corretta
return( question.isCorrectAnswer( answer));
else return( null);
17
18
19
20
21
22
}
23
Button.MouseReleased()
listener
• Per tutte le istanze di Question salvate nell’attributo questionList, chiamate getAnswersCorrecteness() e definisci il comportamento del programma secondo le specifiche
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void mouseReleased(MouseEvent e) {
// crea quantita’ interessanti per questa funzione
Integer correctCounter = 0;
Boolean incomplete = false;
List< Integer> wrongAnswer = new ArrayList< Integer>();
// per tutte le domande in questa finestra
for( int i = 0; i < questionList.size(); i++){
// ottieni la selezione della domanda
// true -> corretta, false -> sbagliata, null ->
assente
Boolean answer = questionList.get(
i).getAnswersCorrecteness();
if( answer != null){
if( answer){
// se e’ corretta contala
correctCounter = correctCounter +
1;
} else {
// se e’ sbagliata salva il suo
numero
wrongAnswer.add(
questions.get(i).getQuestionNumber());
}
} else {
// se non c’e’ una risposta alza il flag e
esci
incomplete = true;
break;
}
25
26
27
28
29
30
31
32
}
// se il flag e’ alto ritorna un’errore
if( incomplete){
displayPanel( " non hai risposto a tutte le
domande, si prega di farlo",
"Questionarion non
Completato",JOptionPane.ERROR_MESSAGE);
} else {
// se il flag e’ basso mostra informazioni
4.5. ESERCIZIO 4.2: CREARE UN FILE CLICCABILE PER LANCIARE UN PROGRAMMA15
displayPanel( " Grazie per la partecipazione al
test." + System.getProperty( "line.separator") +
" Risposte corrette " +
correctCounter + ". Numero delle risposte sbagliate: " +
wrongAnswer,
"Questionarion
completato",JOptionPane.INFORMATION_MESSAGE);
}
33
34
35
36
37
4.5
}
Esercizio 4.2: Creare un file cliccabile per
lanciare un programma
Una caratteristica che abbiamo visto all’inizio di questo capitolo è quella di poter
esportare un programma per tutti i sistemi operativi muniti di JVM. Vediamo
come è possibile farlo su Eclipse. Per prima cosa andate su:
File
Export
Java
Runnable Jar File
e premete Next. Digitate il percorso e il nome del file che volete creare e premete
Finish. Ora sarà possibile fare doppio click sul file appena creato per lanciare
l’applicazione. Se non dovesse funzionare, si può scegliere (con l’istruzione apri
con) l’applicazione java più consona al programma creato, solitamente OracleJava. Se anche questo metodo dovesse fallire da un qualsiasi prompt di comandi
potete navigare fino al file che volete lanciare e scrivere:
1
java -jar nameOfFile.jar
Da notare che all’interno delle opzioni di Export, viste poco prima, c’è anche
l’opzione Jar File che permette, con operazioni del tutto analoghe alle precedenti, di creare l’archivio Jar per esportare le vostre classi come librerie. Esattamente il metodo con cui è stato creato il pacchetto FileManager.jar visto nella
precedente esercitazione.