Sviluppo

Come faccio? - parte XIII

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 2 numero 11

Salve. Sto preparando la beta di Smalled 2.0 e sto incappando in tutta una serie di interessanti problemi. Per esempio, volevo dare la possibilità di inserire data e ora nel file o di stamparle nell'intestazione. Ho scoperto che usando semplicemente le informazioni di nazionalità non si ottenevano i risultati voluti. Il fatto è che potete cambiare le impostazioni da Pannello di controllo/Nazione. I programmi OS/2 devono seguire queste impostazioni! Ovviamente volevo farlo anch'io. Nessuno sembrava sapere come questo funzionasse. Così ho fatto un po' di ricerca e ho scoperto un mucchio di informazioni sul file OS2.INI. Sapevo che era lì, ma non avevo mai compreso tutte le sue potenzialità finora. Perciò è molto probabile che un futuro articolo si occupi di queste cose.

Cosa faremo questo mese? Penso sia arrivato il momento di parlare dei controlli e di come usarli. Nell'ultimo anno abbiamo usato un solo controllo, un pulsante. Penso sia ormai tempo di parlare di qualcosa di più interessante. Potreste aver bisogno di rileggere alcuni degli articoli passati per capire di cosa sto parlando questo mese. Comunque daremo uno sguardo generale ad alcuni controlli e rispolvereremo le nostre conoscenze su alcuni dei messaggi più generali.

Cos'è un controllo? La risposta è essenzialmente semplice: è una finestra. Perciò tutte le cose che vediamo di solito dentro un dialog, come pulsanti, barre di scorrimento, campi di testo eccetera sono finestre. Questo può essere una sorpresa, ma se ci pensate un po' ha una sua logica. Di una finestra sappiamo che è perfettamente adatta al paradigma ad eventi. Ha una window procedure in cui gestisce i messaggi che le vengono inviati. Perciò quando premiamo un pulsante, vengono inviati dei messaggi alla window procedure del pulsante che lo fa comportare come un pulsante. Lo disegnerà premuto quando ci premete il pulsante del mouse sopra, e lo rialzerà quando lo solleverete. Inoltre, possiamo farle generare altri messaggi come WM_COMMAND e inviarli alla window procedure della finestra principale così che il pulsante si comporti come una voce di menu. Avrete indovinato che ogni controllo ha il suo insieme di messaggi che può gestire nella propria window procedure. Vedremo in un prossimo articolo come possiamo modificare la window procedure dei controlli per personalizzarli (questa tecnica viene chiamata subclassing).

Per ora non ci addentreremo nei messaggi specifici dei vari controlli, ma prenderemo in considerazione un messaggio che ha a che fare con tutti i controlli: WM_CONTROL. Questo è un messaggio di notifica. Un controllo invia il messaggio WM_CONTROL al suo proprietario per informarlo di un particolare evento. Hmm, cosa ce ne facciamo? Potremmo modificare quello o altri controlli a seguito di tale messaggio. Qualche esempio:

  • Generare una scritta su una barra di stato in base a un WM_CONTROL che ci informa di una modifica nel controllo MLE (Multi Line Edit). Ad esempio i numeri di riga e colonna.
  • Disabilitare alcuni controlli se una casella di spunta è attivata
  • Salvare alcune impostazioni a seguito di un messaggio di cambiamento
  • Effettuare controlli sui dati
  • Gestire messaggi d'errore

La cosa buona è che possiamo farlo nella window procedure del proprietario. Ovvero, non dobbiamo intervenire nella window procedure del controllo per ottenere questi risultati! Vedremo un po' di codice tra poco, ma prima esaminiamo il messaggio WM_CONTROL.

Quando un controllo invia un messaggio al proprietario, i due parametri mp1 e mp2 conterranno le seguenti informazioni:

mp1

   USHORT id            // Indentitificatore del controllo.

   USHORT usnotifycode  // Codice di notifica, specifico del controllo



mp2

   ULONG  ulcontrolspec // Codice sppecifico del controllo
L'identificatore. Abbiamo visto costruendo menu che ogni voce ha il suo id. È la stessa cosa per ogni controllo in una finestra o un dialog. Anche ogni parte di finestra come la barra del titolo, i pulsanti di ingrandimento/riduzione ecc. hanno degli id. Sono semplicemente dei numeri che identificano il controllo. È consigliabile dare ad ogni controllo in una finestra il suo id univoco così da poterlo riconoscere. Con ogni editor di dialog potete assegnare in maniera semplice un id a ciascun controllo. Possiamo anche generare controlli da programma usando la funzione WinCreateWindow. In questo caso l'id viene fornito dalla funzione stessa.

Il campo usnotifycode contiene la notifica che il controllo vuole inviare. Ogni controllo ha un insieme di numeri che indicano i vari eventi che può notificare. Ad esempio, quando avviene un cambiamento in un MLE questo campo conterrà il valore MLM_CHANGE. Quando premete la freccia in alto di uno spin button, questo campo conterrà il valore SPBN_UPARROW.

Il campo ulcontrolspec contiene informazioni specifiche in relazione al codice usnotifycode. Ad esempio, la posizione di una barra di scorrimento.

I valori associati a costanti come MLM_CHANGE e SPBN_UPARROW sono predefiniti nei file header forniti con il vostro compilatore o con l'OS/2 toolkit.

Potrebbe esservi sorta una domanda. Come faccio a mettere due valori in un parametro del messaggio? Il parametro è un long, quindi occupa 4 byte (32 bit) di memoria. Perciò potremmo metterci fino a 32 dati da 1 bit l'uno. Allo stesso modo, dovremmo poterci mettere 2 dati da 16 bit. Uno short è lungo 16 bit, quindi possiamo mettere 2 short in un long. Ci sono delle comode macro predefinite per inserire valori come questi in un message parameter, o per estrarle di nuovo. Per estrarre uno short da un long possiamo usare SHORT1FROMMP o SHORT2FROMMP. Queste ritornano il primo o il secondo short dal message parameter. Ad esempio:

SHORT2FROMMP(mp1) ritorna il secondo short da mp1.

Per riempire un message parameter possiamo usare MPFROM2SHORT nella forma MPFROM2SHORT(short1,short2).

Con queste conoscenze di base possiamo creare un dialog molto semplice per vedere quando vengono invati i messagggi WM_CONTROL. Per farlo useremo il programma del mese scorso e lo modifcheremo. Ho cambiato il menu ed ora contiene solo la voce 'Exit'. Questo vi permetterà di uscire dall'applicazione. A parte questo, ci sono 3 controlli nella client area; due caselle di testo e un MLE. Le caselle di testo sono raggruppate così che potete passare da una all'altra con i tasti cursore.

L'idea è di catturare i messaggi WM_CONTROL generati dalle caselle di testo e mostrare alcune informazioni su di essi nel MLE. In questo modo potremo ottenere qualche indizio su cosa sta accadendo. La scelta è caduta sulla casella di testo perchè non ha bisogno di inizializzazione e la stuttura del suo messagio WM_CONTROL è piuttosto semplice.

Come gestiamo il tutto? Per prima cosa dobbiamo intercettare i messaggi WM_CONTROL nella window procedure del dialog. Questo è semplice: aggiungiamo un 'case:' per WM_CONTROL. In questa parte di codice vorremmo costruire una stringa che contenga le informazioni da mostrare nel MLE. Per generare la stringa useremo una funzione strandard del C, sprintf. Per usare questa funzione dovete includere il file <stdio.h>.

Prima di tutto vediamo quale casella ha inviato WM_CONTROL. Questa informazione è contenuta nel primo short del message parameter1 di WM_CONTROL . Con il codice seguente inseriamo nella stringa achControl il testo "Entry1" o "Entry2".

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

// Determina quale casella ha inviato il messaggio

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

switch (SHORT1FROMMP(mp1))

    {

    case ENTRYFIELD1: sprintf(achControl,"Entry1 "); break;

    case ENTRYFIELD2: sprintf(achControl,"Entry2 "); break;

    }
Poi vogliamo sapere quale notifica è stata inviata. Possiamo farlo esaminando il secondo short di mp1. Il codice seguente si occupa di questo.
//------------------------------------------------------------------

// Determina quale notifica è stata inviata

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

switch (SHORT2FROMMP(mp1))

    {

    case EN_CHANGE:    sprintf(achNotify, "EN_CHANGE \n");    break;

    case EN_KILLFOCUS: sprintf(achNotify, "EN_KILLFOCUS \n"); break;

    case EN_MEMERROR:  sprintf(achNotify, "EN_MEMERROR \n");  break;

    case EN_OVERFLOW:  sprintf(achNotify, "EN_OVERFLOW \n");  break;

    case EN_SCROLL:    sprintf(achNotify, "EN_SCROLL \n");    break;

    case EN_SETFOCUS:  sprintf(achNotify, "EN_SETFOCUS \n");  break;

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

    // In caso di messaggi non documentati come EN_INSERTMODETOGGLE, EN_UPDATE ecc.

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

    default: sprintf(achNotify, "%x \n", SHORT2FROMMP(mp1));  break;

    }
Il primo segmento di codice era abbastanza semplice. Questo richiede invece qualche spiegazione. Stampa il codice di notifica nella stringa achNotify, seguito da un 'a capo'. Ho tenuto conto di tutti i sei messaggi documentati relativi alla casella di testo, ovvero:

EN_CHANGE: Il contenuto è stato cambiato, e il cambiamento è già visibile.

EN_KILLFOCUS: la casella sta perdendo il focus. Questo accade quando passate all'altra casella.

EN_MEMERROR: la casella non può allocare la memoria necessaria per contenere il testo. Questo messaggio non dovrebbe mai presentarsi. La dimensione della memoria necessaria è, nel nostro caso, il valore assunto di 16 byte. In futuro vedremo un modo di aumentare questo valore.

EN_OVERFLOW: la casella non può inserire testo oltre il limite corrente. Nel caso dell'esempio questo limite è di 16 caratteri.

EN_SCROLL: la casella sta per scorrere orizzontalmente. Potete provarlo riempiendo completamente la casella e spostandovi da un estremo all'altro con i tasti cursore.

EN_SETFOCUS: la casella sta ricevendo il focus.

Dopo aver implementato questo, ho notato che ci sono alcuni messaggi che non rientrano nei sei che avevo definito, quindi ho aggiunto la linea 'default:'. Questa line viene eseguita se SHORT2FROMMP(mp1) non rientra nei sei casi precedenti. Possiamo concludere che la casella di testo sta usando dei messaggi non documentati! Ho fatto una ricerca tra i vari file header e ho scoperto che in effetti ci sono dei messaggi EN_ di notifica per la casella di testo in PMWIN.H e altri file. Tra questi ci sono EN_INSERTMODETOGGLE e EN_UPDATE. Per quanto riguarda EN_UPDATE posso capire cosa faccia. È simile a EN_CHANGE, ma viene inviato prima di mostrare i cambiamenti sullo schermo. Non ho invece la più pallida idea di cosa significhi EN_INSERTMODETOGGLE. Forse qualcuno può illuminarmi?

Bene, cosa c'è poi? Dobbiamo mettere il testo nel MLE. Come ho detto prima, è possibile inviare un messaggioa un controllo per fargli fare qualche azione. Possiamo usare WinSendMsg, ma dobbiamo conoscere l'handle del controllo. Non conosciamo l'handle del MLE, solo il suo id. Ci sono vari modi di uscirne. Io preferisco farlo fare ad una macro. Possiamo ottenere l'handle di una finestra se conosciamo il suo id e il suo parent, tramita la funzione WinWindowFromID. Questa funzione viene usata dalla macro WinSendDlgItemMsg, quindi preferisco quest'ultima. Le due chiamate seguenti sono identiche:

1) WinSendMsg(WinWindowFromID(hwndDlg, id), ulMsg, mp1, mp2);
2) WinSendDlgItemMsg(hwndDlg, id, ulMsg, mp1, mp2);

Quale messaggio usiamo per inserire del testo in un MLE? Ci sono di nuovo diverse alternative, ma in questo caso preferisco usare MLM_INSERT. Per MLM_INSERT, mp2 è vuoto e mp1 contiene il puntatore alla stringa. Quindi il testo ottenuto prima può essere mostrato nel MLE con il codice seguente:

   WinSendDlgItemMsg(hwndDlg, MLE1, MLM_INSERT, achControl, 0l);

   WinSendDlgItemMsg(hwndDlg, MLE1, MLM_INSERT, achNotify, 0l);
Di una cosa dobbiamo preoccuparci: non vogliamo mostrare il testo se è stato generato da un messaggio WM_CONTROL proveniente dal MLE. (Questo porterebbe ad un ciclo infinito). Perciò controlliamo che non venga dal MLE esaminando mp1.
if (SHORT1FROMMP(mp1) != MLE1)
Bene, è stato un po' difficile questo mese, ma avevamo bisogno di conoscere WM_CONTROL prima di poter fare qualcosa di serio. Prendete pure programma e sorgente (ZIP, 19.2k) dell'esempio di questo mese. Il prossimo mese approfondiremo un controllo in particolare per ottenere una prima piccola applicazione. Ho già qualche idea, ma chiunque abbia un'idea per una piccola e semplice applicazione può scrivermi. Potrei cambiare la mia idea originale :-) Per ora, vi auguro un mese fruttuoso.


[Pagina precedente] [Sommario] [Pagina successiva]