MaCocoa 010

Capitolo 010 - Attività di contorno

Approfondimenti con una nuova applicazione.

Sorgenti: Varie fonti

Primo inserimento: 21 Novembre 2001

Ritocchi per aggiornamento XCode 2.1: 8 Agosto 2005

Usare IB e Xcode

Dopo la realizzazione della prima applicazione in Cocoa, passiamo subito ad una seconda applicazione, rapida e veloce da realizzare, ma che ci permette di esaminare alcuni aspetti trascurati nella (frettolosa) realizzazione dell'applicazione Contatore.

Nota dell'Agosto 2005: all'epoca era un argomento d'attualità; adesso lo è un po' meno, nonostante ricorrenti dichiarazioni di uomini politici. Rimane comunque un utile esercizio.

Questa nuova applicazione permette di calcolare la conversione in Euro tra le valute che partecipano all'unificazione monetaria europea. La cosa è programmaticamente semplice, ma ci sono alcune cose che saranno approfondite oppure esaminate per la prima volta:

EuroConv

figura 01

figura 01

L'applicazione EuroConv presenta una interfaccia molto semplice: ci sono due campi di testo per contenere l'uno l'importo in Euro e l'altro l'importo in una delle valute dell'area Euro. Un menu poop-up permette di selezionare la valuta che si intende utilizzare (lire, marchi, franchi, eccetera), e contestualmente si modifica una stringa di testo che informa sul tasso di cambio in Euro di quella particolare valuta. Faccio prima a mostrare la figura che a descrivere oltre la cosa.

L'idea è che la semplice scrittura di un valore in uno dei due campi scateni la conversione (in un senso o nell'altro) dell'importo. D'altra parte, il cambio di valuta mantiene fisso il valore in Euro e converte il campo della valuta locale. In questo modo, se voglio sapere a quanti franchi francesi corrispondono mille lire, parto dalla valuta italiana, scrivo mille, automaticamente l'importo in euro è aggiornato, seleziono dal menu i franchi, ed ottengo il corrispondente in franchi del valore in Euro (che corrisponde appunto a quello di partenza in lire).

L'interfaccia

figura 02

figura 02

Il progetto nasce velocemente: faccio un nuovo progetto in XCode, lo chiamo m010, passo subito in IB per manipolare l'interfaccia. Parto dalla finestra, che costruisco velocemente aggiungendo i due campi testo, la stringa e il menu pop-up. L'aggiunta di tutte le valute è la parte più lunga e noiosa. Per aggiungere voci al menu, ho impiegato qualche minuto per realizzare che si fa drag&drop a partire dall'elemento item della palette dei menu (ovvio, no?).

Già che ci siamo, modifico anche tutti i menu, inserendo il nome dell'applicazione EuroConv in tutti i posti in cui mi pare necessario (nell'About, nel Quit, nello Help). Bene. Adesso comincio a fare le cose che rendono una applicazione Macintosh di tale nome.

figura 03

figura 03

Comincio col disporre accortamente tutti i vari elementi dell'interfaccia. Allo scopo, mi faccio aiutare dalle linee guida, trattate nel capitolo precedente. Dispongo il campo valuta locale in modo che le due linee guida (azzurre nel mio caso) verticali ed orizzontali scattino nei pressi dell'angolo in alto a sinistra della finestra. Dopo di che, allineo il menu pop-upin modo che, di altezza uguale, corrisponda orizzontalmente. Il campo della valuta in Euro lo piazzo sotto il campo testo della valuta locale, e poi aggiungo due testi non editabili per contenere informazioni (in uno ci ho scritto semplicemente Euro e non lo cambierò mai, mentre l'altro campo avrà un contenuto variabile con la voce attiva del menu pop-up... intanto ci metto un testo di default relativo alle lire). Per aiutare ulteriormente il posizionamento degli oggetti, oltre alle linee dinamiche è possibile disegnale delle linee guida aggiuntive (voce Guide dal menu Layout di IB).

Dinamica degli oggetti

Il passo successivo è decidere come si comportano gli oggetti quando cambia la dimensione della finestra.

Per i ridimensionamenti orizzontali, decido che il menu e la scritta Euro rimangono di dimensione fissa ed attaccati al bordo sinistro della finestra, mentre i due campi delle valute si allungano o restringono mantenendo costanti le distanze dai bordi della finestra. Per i dimensionamenti verticali, decido in pratica di riposizionare uniformemente lo tre righe nello spazio disponibile, senza modificare le altezze degli oggetti. Nel terzo superiore rimane quindi il menu e il campo valuta locale, nel terzo centrale le informazioni sulla conversione, nel terzo inferiore la scritta Euro e la valuta corrispondente.

figura 04

figura 04

Per fare ciò, ho a disposizione il pannello Size della finestra di informazioni (che si ottiene facendo Show Inspector dal menu Tools di IB). In questo pannello, oltre a decidere numericamente le dimensioni, ho un disegno (il riquadro Autosizing) che determina il comportamento dell'oggetto selezionato nei confronti dell'oggetto visuale che lo contiene (nel caso, la finestra). Il comportamento si stabilisce impostando i legami del quadrato interno con il quadrato esterno. Tali legami possono essere di due tipi: fissi, corrispondenti a una doppia barra dritta, o dinamici, corrispondenti ad una connessione a molla. È così facile dire come si comportano i vari oggetti. Ad esempio, i campi valuta devono mantenere fisse le distanze dai bordi orizzontali e la loro dimensione verticale, cioè l'altezza. Per fare questo, vi rimando alla figura, cosa che mi risparmia un sacco di parole (ma spiego comunque: occorre mettere molle all'interno in direzione orizzontale, lasciando fisse le barre orizzontali. Viceversa, verticalmente, esternamente si hanno molle ed internamente barre dritte).

È possibile verificare subito cosa succede quando ridimensiono la finestra. Da IB allora scelgo Test Interface, e simulo il comportamento del programma. Mi limito a ridimensionare la finestra, e vedo che i vari elementi dell'interfaccia si comportano come desiderato. Più o meno: ci sono il menu pop-up ed il campo valuta locale che fanno degli strani movimenti, per non parlare del campo valuta in Euro che, dopo un po' di movimento, rimane attaccato al bordo superiore e da lì non si sposta... mah. Speriamo che l'applicazione vera e propria si comporti correttamente....

Se uno non vuole complicarsi troppo la vita, può decidere di mantenere la finestra di dimensioni fisse, e quindi eliminare il problema del ridimensionamento alla radice. Allo stato attuale, l'unico metodo che ho trovato è quello di dire che la finestra ha dimensioni massime e minime coincidenti (sono valori che posso assegnare liberamente da IB, basta selezionare la finestra ed andare sullo Inspector...). In ogni caso, ne approfitto anche per dire che la mia finestra NON avrà il pulsante di chiusura finestra. L'applicazione quando parte apre la finestra, e non c'è verso di fare scomparire questa finestra, se non uscendo dall'applicazione (si può sempre iconizzare, ovviamente). Così facendo, elimino alla radice il problema di aprire una nuova finestra nel caso l'utente decida di chiuderla.

L'applicazione in quattro istruzioni

figura 05

figura 05

Conclusa la preparazione dell'interfaccia, passiamo velocemente alla realizzazione dell'applicazione. Sappiamo già che dobbiamo costruire una classe Controllore, che battezzo immantinente EuroCtrl. Questa classe avrà una unica istanza, un oggetto che chiamo oEuroCtrl. Alla classe associo un po' di azioni (metodi) e outlet.

Le azioni sono tre:

Gli outlet sono anch'essi tre, e corrispondono ai tre elementi dell'interfaccia con i quali si interagisce, ovvero il campo della valuta locale (setLocal), il campo della valuta in Euro (setEuro) ed il campo informazioni (setMsg). Ci metto più tempo a dirlo che a fare che connessioni.

Sempre velocemente, impostiamo le connessioni: dal menu pop-up verso oEuroCtrl con action cvtMsg, dal campo valuta locale verso oEuroCtrl con action cvt2Euro, dal campo valuta in Euro verso oEuroCtrl con action cvt2Local.

A questo punto generiamo lo scheletro del codice sorgente per la classe EuroCtrl, ed il nostro lavoro con IB è terminato.

Un po' di codice

Siamo adesso in XCode. L'applicazione che stiamo facendo è un classico esempio in cui il paradigma M-V-C perde un po' di significato, in quanto la classe modello brilla per la sua assenza (fossimo in SmallTalk, avrei definito una classe Euro come modello, con metodi per conversioni da e verso valute locali, ma sarebbe tirare fuori sangue da una rapa).

Le operazioni da svolgere sono molto banali, anche se occorre scrivere molte righe per organizzarsi. In primo luogo, abbiamo bisogno dei tassi di conversione tra Euro e le altre valute. Utilizzo il buon vecchio #define del linguaggio C per costruirmi degli identificatori adatti. Già che ci sono, mi faccio un po' di identificatori per attribuire dei nomi sensati ai vari paesi, piuttosto che avere a che fare con dei numeri. Sono quindi arrivato a questo punto:

#define        NUM_EURO_NATIONS    11

#define        EURO_ITALY          0
#define        EURO_GERMANY        1
#define        EURO_FRANCE         2
#define        EURO_BELGIO         3
#define        EURO_FINLAND        4
#define        EURO_OLANDA         5
#define        EURO_SPAGNA         6
#define        EURO_PORTOGAL       7
#define        EURO_IRLANDA        8
#define        EURO_AUSTRIA        9
#define        EURO_GRECIA        10

#define        EURO_ITALY_CVT       (1936.27)
#define        EURO_GERMANY_CVT     (1.95583)
#define        EURO_FRANCE_CVT      (6.55957)
#define        EURO_BELGIO_CVT      (40.3399)
#define        EURO_FINLAND_CVT     (5.94573)
#define        EURO_OLANDA_CVT      (2.200371)
#define        EURO_SPAGNA_CVT      (166.386)
#define        EURO_PORTOGAL_CVT    (200.482)
#define        EURO_IRLANDA_CVT     (0.78756)
#define        EURO_AUSTRIA_CVT     (13.7603)
#define        EURO_GRECIA_CVT      (340.75)

figura 02

figura 02

Ho avuto l'accortezza di attribuire i numeri nello stesso ordine con cui ho definito le voci del menu pop-up. Ho infatti sbirciato la definizione dell'oggetto menu pop-up e scoperto che la classe relativa NSPopUpButton ha un metodo dal nome indexOfSelectedItem, che restituisce appunto un indice che determina quale voce di menu è attualmente selezionata. Ciò mi permette di mettere tutti i rapporti di conversione in un vettore e di utilizzare il rapporto corretto in base all'indice dell'elemento selezionato dal menu. Allo scopo ho dunque dichiarato all'interno della classe EuroCtrl due variabili d'istanza di questo tipo:

short        currSel ;
float        cvtRate[ NUM_EURO_NATIONS ];

La prima mi serve per tenere traccia dell'elemento selezionato (avrei potuto fare altrimenti: aggiungere un outlet ed attribuirgli un collegamento al menu pop-up, ed interrogarlo ogni volta che mi serviva sapere quale fosse la valuta in gioco), la seconda è un vettore che tiene conto di tutti i rapporti di conversione. Questo vettore va ovviamente riempito.

Svegliati!

Nel capitolo precedente, queste operazioni di inizializzazione erano svolte all'interno di un metodo init che sovrascriveva quello standard. Ho scoperto un metodo differente e direi migliore (non c'è bisogno di fare l'inizializzazione completa), valido però solo per gli oggetti che si trovano dentro un file nib e che sono creati automaticamente (dall'ambiente Cocoa) al lancio dell'applicazione.

Infatti, quando l'ambiente Cocoa lancia l'applicazione, carica il file nib principale (qui ce ne è uno solo, quindi MainMenu.nib) e costruisce tutti gli oggetti presenti in esso. Di seguito, prima di rappresentarli a video, invia a ciascuno di essi il messaggio awakeFromNib (senza argomento, e senza attendersi risposta). È questo un ottimo posto per fare un po' di cose. Ad esempio, riempire il vettore con i rapporti di conversione, e poi impostare valori di default per i vari campi (se mai volessi qualcosa diverso dallo zero che ho impostato in IB).

Ecco quindi la mia realizzazione del metodo (un po' noioso, mi rendo conto):

- (void) awakeFromNib
{    
    cvtRate[ EURO_ITALY ]         = EURO_ITALY_CVT ;
    cvtRate[ EURO_FRANCE ]         = EURO_FRANCE_CVT ;
    cvtRate[ EURO_GERMANY ]     = EURO_GERMANY_CVT ;
    cvtRate[ EURO_BELGIO ]         = EURO_BELGIO_CVT ;
    cvtRate[ EURO_FINLAND ]     = EURO_FINLAND_CVT ;
    cvtRate[ EURO_SPAGNA ]         = EURO_SPAGNA_CVT ;
    cvtRate[ EURO_OLANDA ]         = EURO_OLANDA_CVT ;
    cvtRate[ EURO_PORTOGAL ]     = EURO_PORTOGAL_CVT ;
    cvtRate[ EURO_IRLANDA ]     = EURO_IRLANDA_CVT ;
    cvtRate[ EURO_AUSTRIA ]     = EURO_AUSTRIA_CVT ;
    cvtRate[ EURO_GRECIA ]         = EURO_GRECIA_CVT ;
    
    currSel = 0 ;
}

Passiamo adesso all'azione (metodo) per la conversione da valuta locale in Euro, ovvero cvt2Euro. Sono riuscito a farcela in una sola riga:

- (IBAction)cvt2Euro:(id)sender
{
    [ setEuro setFloatValue: [ sender floatValue ] / cvtRate[ currSel ] ] ;
}

Vediamo bene: sender è l'oggetto che ha inviato la richiesta: si tratta del campo che contiene la valuta locale. Allora, lo uso per ricavare il valore lì presente:

[ sender floatValue ]

il messaggio è parente del metodo visto nel capitolo precedente per impostare un valore intero all'interno di un campo testo. Lì si usava setIntValue, qui usiamo floatValue se vogliamo estrarre un valore floating point (con la virgola) o anche intValue se si intende estrarre un valore intero.

Torniamo all'euroconvertitore. Uso la variabile currSel per determinare quale rapporto di conversione utilizzare:

cvtRate[ currSel ]

Qui le parentesi quadre individuano un vettore, e non sono da confondere con le parentesi quadre che invece (come nel pezzetto precedente) indicano la trasmissione di un messaggio.

Piglio il valore da convertire e lo divido per il rapporto di conversione, in modo da avere l'ammontare in Euro:

[ sender floatValue ] / cvtRate[ currSel ]

Questa espressione è un numero floating point, e lo uso come argomento del messaggio setFloatValue inviato al campo che mostra la valuta in Euro, individuato dall'outlet setEuro:

[ setEuro setFloatValue: <valore> ]

Finito.

Vi risparmio il commento sull'altro metodo che trasforma gli Euro in valuta locale:

- (IBAction)cvt2Local:(id)sender
{
    [ setLocal setFloatValue: cvtRate[ currSel ] * [ sender floatValue ] ] ;
}

L'ultimo metodo

L'ultimo metodo, quello che scatta quando l'utente cambia la voce del menu, comporta molto lavoro aggiuntivo, dovuto essenzialmente al cambiamento del messaggio informativo.

Se infatti lo ignorassimo, il metodo sarebbe molto semplice, due istruzioni:

- (IBAction)cvtMsg:(id)sender
{
    currSel = [ sender indexOfSelectedItem ];
    [ setLocal setFloatValue: [ setEuro floatValue ] * cvtRate[ currSel ] ] ;
}

Con la prima istruzione, cambio il valore della variabile d'istanza currSel per tenere traccia della variazione del menu; per fare questo, basta inviare il già citato messaggio indexOfSelectedItem all'oggetto scatenante il metodo, che è proprio il menu pop-up. La seconda istruzione non è altro che un altro modo di attribuire un valore al campo della valuta locale, pigliando il valore direttamente dal campo utilizzando l'outlet che lo identifica (avrei potuto usare la stessa espressione anche nel metodo cvt2Local, ma così avrei perso l'argomento sender, che sarebbe servito a nulla...).

Ma ci sono le stringhe da modificare. E questo apre un interessante problema di localizzazione e di gestione delle risorse che discuterò nel prossimo capitolo, a cui pieroangelicamente vi rimando.

Licenza Creative Commons
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto Licenza Creative Commons.
Pagina a cura di Livio Sandel (macocoa2012@gmail.com).