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.
© Copyright 2024 Paperzz