Sviluppo

Come faccio? - parte XVI

Gianni Ceccarelli
 

stesura originale in inglese di Eric Slaats - liberamente tradotto da Gianni Ceccarelli
comparso per la prima volta su Os/2 e-Zine! volume 3 numero 4

Salve a tutti! Di cosa ci occuperemo questo mese? Continueremo a migliorare la prima versione della calcolatrice e aggiungeremo qualche funzione. Le aggiunte sono abbastanza piccole, ma ne discuteremo piuttosto estesamente. Penso che in questo articolo la teoria sarà superiore alla pratica.

OK, iniziamo con le migliorie. Ho ricevuto un messaggio da Cindy, che mi ha fatto notare un modo di semplificare il mio codice. Nello scorso articolo ho presentato un modo per togliere gli zeri finali e il punto dal risultato, usando questo codice:


   sprintf(achOutput, "%f", flLeftMember);



   while (achOutput[strlen(achOutput)-1] == '0')   // togli gli zeri

           achOutput[strlen(achOutput)-1] = 0;

   if (achOutput[strlen(achOutput)-1] == '.')      // togli il punto

           achOutput[strlen(achOutput)-1] = 0;

Cindy mi fa notare che si può sostituire con questo:

    sprintf(achOutput, "%g", flLeftMember);

Questo approccio non solo riduce le dimensioni del codice, ma gestisce anche il caso dei numeri espressi in forma esponenziale. Grazie, Cindy; starei ancora a pensare a un modo per risolvere il problema.

Mi sono ritrovato a usare sempre più la calcolatrice per calcoli semplici. Usandola, ho trovato alcune cose fastidiose. Indovinato -- ce ne occuperemo, sperando di impararne qualcosa.

Una delle cose che non mi piacciono è di non poter usare il backspace se inserisco una cifra sbagliata. Come in una calcolatrice normale, devo premere il tasto di cancellazione. Questo però cancella tutto il numero, non solo la cifra sbagliata -- questo è il lato negativo di aver usato i tasti 'mnemonici' dei dialog. L'entryfield è di sola lettura, quindi non può gestire il backspace al posto nostro. Questo significa che dovremo trovare il modo di gestire l'evento della pressione del tasto backspace.

Prima di tutto, come facciamo a sapere che è stato premuto un tasto, e in particolare il backspace? Ovviamente OS/2 ha un messaggio apposito: WM_CHAR. Questo messaggio viene inviato alla finestra attiva ogni volta che l'utente preme un tasto. I parametri del messaggio WM_CHAR contengono le informazioni sul carattere premuto, ma qui le cose cominciano a diventare complesse. I parametri sono impostati come segue:


mp1 31------24 L'ultimo byte (bit 31-24) contiene lo scan code

    23------16 Questo byte contiene il conteggio di ripetizione

    15------00 Il primo short contiene dei flag (ne parliamo dopo)



mp2 Lo short superiore contiene il codice di tasto virtuale (virtual key)

    Lo short inferiore contiene il codice carattere (16 bit)

Credo che ora serva qualche spiegazione. Ci sono tre codici di tasto in questi parametri, quindi li esamineremo rapidamente. Da mp1: lo scan code. Questo codice viene generato dal PC. Potete essere ben sicuri che sarà diverso con tastiere diverse, quindi è meglio non usare questo valore. Se lo fate, dovrete probabilmente modificare il programma per ogni tipo di macchina.

Il codice di tasto virtuale è contenuto nello short superiore di mp2. I tasti virtuali sono ad esempio i tasti funzione, il delete, il backspace, ecc. -- in pratica, tutti i tasti non caratteri, esclusi <ALT>, <SHIFT> e <CTRL>. Torneremo a parlarne tra poco, visto che il backspace è tra questi.

L'ultimo codice è il codice carattere contenuto nello short inferiore di mp2. Contiene il codice ASCII del tasto premuto, per cui sarà influenzato da <SHIFT> e <CTRL> (anche se <CTRL> è un caso particolare).

Ci serve il backspace. Come sappiamo se è premuto? Ho detto che è un tasto virtuale. Ci sono vari modi per leggere il campo apposito dai parametri di WM_CHAR. Potremmo separarli a mano, ma c'è un modo più semplice. L'API di OS/2 contiene una macro per gestire il messaggio WM_CHAR. Se per sempio voglio il flag fs da mp1, posso scrivere:


    CHARMSG(&msg)->fs

Possiamo confrontarlo con un altro valore, quindi per sapere se è premuto un tasto virtuale, possiamo scrivere:

    CHARMSG(&msg)->fs & KC_VIRTUALKEY

(Per ora non ci occupiamo dei flag.) Allo stesso modo possiamo estrarre il codice del tasto. Ogni tasto virtuale in OS/2 ha un valore definito. Questi valori sono contenuti delle costanti VK_ . Per esempio VK_F1 è il tasto funzione 1, VK_LEFT è il tasto cursore a sinistra e VK_BACKSPACE è il tasto backspace (tutte queste costanti si posso trovare nella descrizione delle API). Sapendo questo è facile gestire il tasto backspace -- basta confrontare il valore di vkey con VK_BACKSPACE. Abbiamo quindi questo if all'inizio del gestore di WM_CHAR:

    if (CHARMSG(&msg)->vkey == VK_BACKSPACE)

Notate che gestire gli eventi di tastiera è un'arte a sé. Qui abbiamo solo scalfito la superficie.

Ora che sappiamo riconoscere il backspace, scriviamo il gestore dell'evento. Vogliamo eliminare l'ultimo carattere della stringa contenuta nell'entryfield. Per prima cosa dobbiamo quindi otttenere tale stringa. Il modo più semplice di procedere è mettere un carattere NULL nel penultimo posto della stringa. Questo accorcerà la stringa di un carattere, poiché le stringhe in C sono terminate da NULL. Per inserirlo, dobbiamo conoscere la lunghezza della stringa. Possiamo agire sulla stringa solo se ha una lunghezza maggiore di zero, quindi anche per questo ci serve la lunghezza. Otteniamo il codice seguente:


//-----------------------------------------------------------------------

// Gestisce il tasto backspace.

//-----------------------------------------------------------------------

case WM_CHAR:

    {

    if (CHARMSG(&msg)->vkey == VK_BACKSPACE)

        {

        char achValue[32];

        long ValueLength;



        WinQueryDlgItemText(hwndDlg , ENTRYFIELD1, 32, achValue);

        ValueLength = strlen(achValue);



        if (ValueLength)

            {

            achValue[ValueLength-1] = 0;

            WinSetDlgItemText(hwndDlg, ENTRYFIELD1, achValue);

            }

        }

    }

break;

La seconda cosa che volevo cambiare è la gestione del 'rotolino': un'opzione per azzerarlo. Inoltre, voglio poter trasferire un numero dal rotolino all'entryfield. Entrambe le aggiunte sono piuttosto facili da implementare.

Prima l'azzeramento. Vogliamo una voce di menu che selezionata elimini tutte le linee della lista. Dobbiamo prima espandere il menu. Ho aggiunto un menu 'edit' e inoltre un box di 'about' associato ad una voce di menu. Dobbiamo quindi gestire questi nuovi eventi.

Come svuotiamo un listbox? In casi come questi, di solito cerco tra i vari messaggo. È un'azione così comune che deve esserci un messaggio apposito. E infatti c'è -- LM_DELETEALL. Quindi ci basta inviare un messaggio:


//-------------------------------------------------------------

// Azzera il 'rullino'

//-------------------------------------------------------------

case IDM_CLEARTALLY:

    {

    WinSendDlgItemMsg(hwndDlg, LISTBOX1, LM_DELETEALL, 0L, 0L);

    }

return(0);

Ora vediamo il trucco del trasferimento. Voglio poter fare click su un numero nel listbox per trasferirlo nell'entryfield. Inoltre, cancellerà il numero lì presente. Siccome questa azione ha un effetto distruttivo sul numero inserito, dobbiamo fare in modo che non sia possibile eseguirle accidentalemnte, per cui un click singolo non va bene. Ci serve almeno un doppio click (è da fare per sbaglio). Per prima cosa dobbiamo riconoscere un doppio click nel listbox.

Qualche articolo fa ho parlato del messaggio WM_CONTROL. Questo messaggio notifica eventi che avvengono in un controllo. Il doppio click è un evento tipico per un controllo, in questo caso un listbox. Scorrendo le notifiche per il listbox, troveremo che quella che ci serve è LN_ENTER. Poiché c'è un solo listbox nella nostra applicazione, possiamo reagire direttamente alla notifica LN_ENTER. Visto che è l'unica notifica che esaminiamo gestendo il messaggio WM_CONTROL ci basta un if:


    if (SHORT2FROMMP(mp1) == LN_ENTER)

Per prima cosa ci serve il contenuto della linea selezionata; per questo ci serve il numero della linea. Per ottenerlo c'è una comoda macro, WinQueryLboxSelectedItem. Dopo questo possiamo recuperare il contenuto. Il codice seguente fa il tutto:

hwndLbox  = WinWindowFromID(hwndDlg, LISTBOX1);

lSelected = WinQueryLboxSelectedItem(hwndLbox);

WinQueryLboxItemText(hwndLbox, lSelected, achListValue, 32);

Ora che abbiamo il contenuto della linea, dobbiamo pensare a un modo per usarlo. Non tutte le linee possono essere copiate nell'entryfield; alcune linee contengono solo un operatore o un uguale. Quindi, dobbiamo controllare il contenuto della linea se ha lunghezza 1. Nel caso, se non contiene una cifra, non può essere inserita nell'entryfield. Con questo possiamo scrivere:

case WM_CONTROL:

    {

    if (SHORT2FROMMP(mp1) == LN_ENTER)

        {

        //-------------------------------------------------------------

        // Recupera linea selezionata

        //-------------------------------------------------------------

        char achListValue[32];

        HWND hwndLbox;

        long lSelected;



        hwndLbox  = WinWindowFromID(hwndDlg, LISTBOX1);

        lSelected = WinQueryLboxSelectedItem(hwndLbox);

        WinQueryLboxItemText(hwndLbox, lSelected, achListValue, 32);

        //-------------------------------------------------------------

        // Controlla che sia un numero

        //-------------------------------------------------------------

        if (strlen(achListValue) !=1 ||

            (achListValue[0] != '=' && achListValue[0] != '-' &&

             achListValue[0] != '+' && achListValue[0] != '*' &&

             achListValue[0] != '/'))

            //--------------------------------------------------------

            // Copia il contenuto

            //--------------------------------------------------------

            WinSetDlgItemText(hwndDlg, ENTRYFIELD1, achListValue);

        }

    }

break;

Bene, per questo mese basta. Il mese prossimo ci occuperemo di codice piccolo. Una delle cose che non mi piacciono del pacchetti software moderni è che sono enormemente grossi e pesanti. Secondo me, in molti casi si può evitare una tale dimensione. Ci sono dei semplici trucchi per evitare che il codice diventi troppo grande.

Al prossio mese.

Sorgente per l'esempio di questo mese: sample16.zip (24K)


[Pagina precedente] [Sommario] [Pagina successiva]