Introduzione alle API Win32

Capitolo 33: i parametri del grafico

Nella applicazione di "grafico di funzione", che stiamo sviluppando come esercizio, l'utente può immettere dati in sei controlli di edit:

[Inoltre, la checkbox (automatica) IDC_AUTOY ha una interazione con IDC_MINY e IDC_MAXY, che dobbiamo ancora chiarire].

Come accorgerci dei cambiamenti che l'utente può apportare a questi controlli, convalidare che il testo immesso o modificato soddisfi i rispettivi vincoli (di tipo sintattico o semantico), e "tenerne traccia" nel nostro codice applicativo?

Abbiamo visto due notifiche, EN_UPDATE ed EN_CHANGE, che un controllo di edit spedisce al dialogo che lo contiene quando è modificato (prima di aggiornare il proprio aspetto, e dopo averlo aggiornato, rispettivamente); ma, nel nostro caso specifico (come spesso succede), non è una grande idea quella di agganciare le nostre "validazioni e aggiornamenti" a queste notifiche. Infatti, le notifiche stesse avvengono "ad ogni piè sospinto": ogni volta che l'utente, premendo un tasto mentre il focus è sull'edit control, modifica sia pur minimamente il testo. Fare una "validazione" (ad esempio della sintassi) a questo punto avrebbe senso solo se si potesse garantire che tutti gli stadi intermedi attraverso cui il testo passa nel corso dell'editing sono anch'essi validi -- e, in pratica, non è quasi mai così.

Pensiamo, ad esempio, al campo "espressione": se l'utente vuole immettere anche una espressione semplicissima, del tipo X+1, ci sarà uno stadio intermedio X+ (subito dopo che è stato digitato il carattere +, subito prima che sia digitato il carattere 1) che valido non è... non avrebbe senso rompere le scatole all'utente, che sta in tutta tranquillità immettendo questa espressione, con l'informazione, momentaneamente lampeggiante da qualche parte sullo schermo, che X+ non è un'espressione valida -- probabilmente lo sa già, e, comunque, che gli importa?!

No, è fondamentale che le nostre verifiche (e i relativi aggiornamenti, se le verifiche sono soddisfatte) avvengano solo quando l'utente considera "concluso" l'editing di ciascun campo. Come facciamo a sapere questo...? Mica possiamo "leggergli il pensiero"...!

No, ma possiamo identificare i due casi in cui le cose stanno certamente così:

Ciascuno di questi due casi, dunque, deve far sì che noi preleviamo il testo dal controllo (ad esempio, con la solita GetDlgItemText), lo verifichiamo (a seconda del tipo di dato che il testo del controllo deve rappresentare), e...:

Ci sono altre strategie di UI (controllare tutto alla fine, elencare tutti i campi problematici...), ma questa, così interattiva, è comoda e attraente per l'utente in molti casi, e non è poi difficile per noi da implementare.

La "qualche parte" cui abbiamo accennato, naturalmente, sarà la solita struttura stru, associata al dialogo, che già abbiamo menzionato (senza ancora dare tutti i dettagli). Dopo tutto, è proprio la funzione indicata nel campo prepara di questa struttura, che userà tutti questi dati (per il calcolo, che già abbiamo mostrato, dell'array di punti, che viene poi usato per la chiamata alla API Polyline, che realizza alla fin fine il vero e proprio disegno del nostro "grafico di funzione").

Consideriamo, ad esempio, il caso della verifica di un edit control dove deve venire immesso un double...:

BOOL verdouble(HWND hd, int id, double* pd, BOOL* cambiato) { char buf[40]; if(!GetDlgItemText(hd,id,buf,40)) return FALSE; char* pfine; double res = strtod(buf,&pfine); if(*pfine) { SetFocus(GetDlgItem(hd,id)); return FALSE; } if(pd && *pd != res) { if(cambiato) *cambiato = TRUE; *pd = res; } return TRUE; }

Questa funzione torna TRUE se il double è OK, o altrimenti FALSE; come effetto collaterale, nel solo caso che il double sia OK, essa può inoltre (se le vengono passati puntatori non nulli) depositare in *pd il nuovo double immesso, e, in *cambiato, un TRUE se il valore è cambiato rispetto al precedente valore di *pd; nel caso di "non OK", la funzione dovrà tornare il focus al controllo contenente il double non corretto (bisognerà, naturalmente, che il controllo che così perde il focus non causi a sua volta un effetto similare!).

Per determinare se il double è OK, usiamo la funzione delle librerie standard C chiamata strtod: essa deposita, nel secondo argomento, il puntatore al primo carattere che non ha considerato parte del double "parsato"; se questo non è lo 0 di terminazione della stringa, allora la stringa (testè estratta dal controllo), che dovrebbe rappresentare null'altro che un double, non è corretta.

 

Il trattamento di un int sarà sostanzialmente analogo, eccetto che le API ci forniscono la GetDlgItemInt, che fa già per conto nostro il grosso del lavoro di verifica; il trattamento della funzione sarà anch'esso abbastanza simile, eccetto che la "verifica" e il "settaggio" avverranno allo stesso tempo, chiamando IndiEs_Espr; non abbiamo incluso in IndiEs una funzione di "confronto" fra espressioni, quindi, a meno che non la aggiungiamo (e potrebbe non essere semplice farla bene, "sapendo", ad esempio, che X+1 e 1+X sono "eguali"... e così pure X*(X+1) e X*X+X...:-), dovremo, prudenzialmente, considerare "cambiata" l'espressione a ogni sua verifica [si può, naturalmente, adottare una qualche posizione intermedia, come, ad esempio, tenere copia del testo che rappresenta l'espressione, sapendo che essa non è cambiata se il testo è ancora eguale; ciò, se non tutti i "ricalcoli inutili", ne risparmierà almeno una certa parte]).

 

Come sempre, quando si cerca di progettare una funzione in modo riusabile, non è chiarissimo quali funzionalità includere in essa, e quali dovranno essere fornite invece altrove; ad esempio, qui abbiamo deciso di includere il concetto di "restituire il focus al controllo non corretto", ma non quello di "messaggio di avvertimento" (che andrà dunque fornito "da fuori", se la funzione torna FALSE). Ma, naturalmente, varie strutture alternative potrebbero essere altrettanto valide, o anche migliori.

Ecco, dunque, in parte, il codice che potremmo usare per validare i vari campi di edit, rispondendo alle loro notifiche:

BOOL valida(HWND hwnd, int id, HWND hwndCtl) { BOOL ok = TRUE; stru* ps = (stru*)GetWindowLong(hwnd,DWL_USER); switch(id) { case IDC_MINX: ok=verdouble(hwnd,id,&ps->minx,&ps->cambiato); break; case IDC_MINY: ok=verdouble(hwnd,id,&ps->miny,&ps->cambiato); break; case IDC_MAXX: ok=verdouble(hwnd,id,&ps->maxx,&ps->cambiato); break; case IDC_MAXY: ok=verdouble(hwnd,id,&ps->maxy,&ps->cambiato); break; case IDC_PASSI: ok=verint(hwnd,id,&ps->nPunti,&ps->cambiato); break; case IDC_FUNZIONE: ok=verfun(hwnd,id,&ps->cambiato); break; default: break; // ignoriamo eventuali altri edit } if(!ok) { SetDlgItemText(hwnd, IDC_STATUS, "Campo non valido -- editalo, per favore!"); } return ok; } void OnDlgCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { switch(codeNotify) { case EN_KILLFOCUS: valida(hwnd,id,hwndCtl); break; case EN_CHANGE: SetDlgItemText(hwnd, IDC_STATUS, ""); break; case BN_CLICKED: { switch(id) { case IDOK: { HWND hFocus = GetFocus(); if(!hFocus || valida(hwnd,GetDlgCtrlID(hFocus),hFocus)) InvalidateRect(GetDlgItem(hwnd, IDC_GRAFICO), 0, FALSE); break; } case IDC_AUTOY: // caso ancora da esaminare...! break; default: break; } } default: break; // ignoriamo gli altri comandi } }

Qui abbiamo deciso di spedire il messaggio d'avvertimento, se del caso, nella funzione valida, che chiama le varie verdouble ecc; il messaggio viene cancellato ogni volta che un qualsiasi controllo di edit è stato cambiato -- operazione che sarà spesso del tutto ridondante, ma, visto che avviene mentre l'utente sta scrivendo, il tempo (molte centinaia di istruzioni di macchina!) così sprecato, sarà di certo del tutto inavvertibile (microsecondi, anche su di una macchina lenta...:-).

Si noti, inoltre, che, se del caso, chiamiamo la valida anche quando viene cliccato (nel senso simulato dall'immissione di un Enter) il bottone "Disegna" (identificatore IDOK), prima di causare (come discusso nella parte precedente) la spedizione del WM_DRAWITEM; questo copre la "anomalia" già più volte menzionata, e garantisce che il disegno, se avviene, avverrà sulla base di dati validi.

Non restano che pochi "frammenti" da chiarire, fra cui la gestione di IDC_AUTOY, che ancora qui abbiamo lasciato commentata... e, naturalmente, lo faremo alla prossima puntata!-)


Capitolo 32: l'impostazione del grafico
Capitolo 34: il grafico: ultimi dettagli
Elenco dei capitoli