Nella applicazione di "grafico di funzione", che stiamo sviluppando come esercizio, l'utente può immettere dati in sei controlli di edit:
IDC_MINX
, IDC_MAXX
, IDC_MINY
, IDC_MAXY
questi corrispondono a numeri di tipo double
IDC_PASSI
questo corrisponde a un numero intero positivo
IDC_FUNZIONE
questo corrisponde al testo di una espressione
(l'espressione di cui disegnare il grafico)
[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...:
IDC_STATUS
per mandare tali
messaggi senza rompere troppo...) e rimettiamo il
focus sul controllo il cui testo non è valido (attenti:
rimettere il focus su di un controllo lo fa perdere ad
un altro controllo -- è importante che questo non
causi un "ciclo di rimbalzi" fra i due, o altri effetti
anomali e imprevisti...!), così che l'utente possa
correggere subito gli errori compiuti.
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...:
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:
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