Abbiamo visto, sinora, come un controllo
static "owner drawn", e alcun funzionalità
elementari degli HDC
di Windows e
relative API, ci permettono di disegnare con facilità
il grafico della nostra funzione, purchè disponiamo
dei dati per questo grafico in forma di un array di
punti da passare all'API Polyline
.
Supponiamo che "tutto il resto" sia già preparato
correttamente: supponiamo, cioè, di conoscere
minx
, max
, se occorre miny
e maxy
(in alternativa, l'informazione che l'utente
desidera che questi parametri siano
determinati automaticamente), e, naturalmente, la
funzione da valutare (nel senso che questa è stata
passata alla IndiEs_Espr
, la quale ha tornato TRUE
indicando così che la sintassi è corretta); inoltre,
supponiamo di conoscere il rettangolo entro cui i
punti da disegnare devono collocarsi, e il numero di
passi da usare per dare maggiore o minore precisione
al grafico (facendo, naturalmente, uno sforzo di calcolo
inversamente proporzionale).
Come procederemmo, allora, per determinare i punti che delineeranno il grafico?
I device context di Windows (HDC
) offrono vari tipi di
funzioni di "mapping" ("scala", isotropica o anche
anisotropica, fra coordinate-utente e coordinate di
schermo in pixel) -- ma tutte si riferiscono a valori
interi per le coordinate; la nostra funzione, invece,
così almeno abbiamo sempre supposto sinora,
viene calcolata in virgola
mobile ("floating point"), ad esempio con dei double
.
Visto dunque che dobbiamo comunque provvedere
alla trasformazione double->int
, tanto vale che noi
stessi ci occupiamo dei fattori di scala, lasciando in
pace il "mapping mode" del device context a qualsiasi
valore esso sia settato (in pratica, sarà sempre di
tipo MM_TEXT
-- strano modo di dire che tutte le varie
coordinate, logiche e fisiche, sono sempre pixel... ma,
comunque, qui poco ce ne importa).
Avremo dunque
dei valori double
, fra minx
e maxx
(ovvero fra miny
e maxy
), da convertire linearmente
in valori int
fra pr->left
e pr->right
(ovvero fra pr->bottom
e pr->top
).
Questa situazione ci suggerisce immediatamente di scrivere una funzione generale
e del tutto riusabile:
Ci sono vari modi di giungere a queste semplici trasformazioni lineari (o altre, che ad esse però siano matematicamente equivalenti). I testi più classici di "computer graphics" mostrano modi vari di ridurre la quantità di calcolo floating point in queste trasformazioni, a spese di un aggravio del calcolo intero -- ma, al giorno d'oggi, con le CPU moderne più diffuse, nulla di più facile che una moltiplicazione intera prenda più tempo di una a virgola mobile, per cui, Dio ci scampi e liberi da tali "ottimizzazioni"...!
(L'unica ottimizzazione "seria", visto che i valori
di min e max saranno costanti per vari punti da
trasformare, sarebbe di precalcolare il fattore
rou/rin
una sola volta -- ma fidiamo che il lettore
sia perfettamente in grado di provvedere a questo,
se lo trova utile!).
Armati, dunque, della imap
, il lavoro di calcolare
i punti del grafico non appare certo arduo -- in
pseudocodice:
x[i]
come double
y[i] = funz(x[i])
[con Indies_valuta
]
miny
e maxy
in modo
automatico (come min e max delle varie y[i]
via via calcolate nel ciclo di cui sopra)
Fidando, ancora una volta, delle capacità del nostro lettore di organizzare questo pseudo-codice come codice funzionante (tutto sommato, esso non ha nulla a che vedere con le API di Windows...), ci resta solo da vedere come potremo determinare, e verificare, tutti quei valori che qui diamo per scontati...:
IndiEs_Espr
(l'utente la
immette come testo nell'edit control IDC_FUNZIONE
,
e...? noi cosa facciamo per validarla e usarla...?)
double
per minx
, miny
,
maxx
, maxy
miny
e maxy
Infine -- come potrà l'utente dirci che tutti questi valori sono a posto, e quindi comandarci di disegnare, infine, il grafico...?
Cominciamo da quest'ultima: l'utente farà un clic sul bottone "Disegna", oppure darà (essendo il focus in un qualsiasi controllo del nostro dialogo) un Enter, il che, in Windows, equivale ad un clic sul pushbutton di default del dialogo (e "Disegna" è appunto... designato come pushbutton di default). A fronte di ciò, cioè nel gestore di questo evento, o messaggio che dir si voglia, cosa dovremo fare noi...?
È presto detto:
WM_DRAWITEM
al nostro dialogo, con tutti
i dati relativi allo static owner-drawn; insomma, che c'è
quel controllo da "ridisegnare", e, quindi, che agisca di
conseguenza.
Quando Windows sa che una finestra va in parte o in tutto
ridisegnata, spedisce un WM_PAINT
alla window procedure
di quella finestra; la window procedure di un controllo owner
drawn, in particolare, raccoglie i dati necessari nella struct
DRAWITEMSTRUCT
già esaminata, e spedisce il messaggio
WM_DRAWITEM
al dialogo. Dunque, quello che ci serve è
far sapere a Windows che la finestra del nostro static di id
IDC_GRAFICO
"non è più valida", cioè, "va ridisegnata".
Per "invalidare" una finestra, o parte di essa, si usa la API:
Il rettangolo, in coordinate-cliente (pixel, come unità di
misura, con l'origine nell'angolo dell'area-cliente della
finestra), identifica la zona da "invalidare"; se si passa un
puntatore 0, si indica che tutta la finestra va invalidata.
Il flag bErase
, se TRUE
, fa si che Windows, prima di
spedire WM_PAINT
, "cancelli lo sfondo", usando il "brush"
("pennello") associato alla classe della finestra; se FALSE
,
questo passo (che in pratica si traduce in un ulteriore
messaggio, WM_ERASEBKGND
) viene risparmiato.
Pensando al nostro controllo owner-drawn: la prima cosa
che facciamo, gestendo il WM_DRAWITEM
, è riempire
l'intero rettangolo con un certo colore -- è inutile dunque
che esso venga prima "cancellato", e possiamo dunque
ottimizzare (sia pure di un nonnulla...)
le cose passando FALSE
come terzo argomento
della InvalidateRect
.
D'altronde, come spesso succede, tutta la finestra deve
essere "ridipinta": inutile, dunque, che ci scervelliamo per
trovare un rettangolo -- tanto vale passare 0
come secondo
argomento della InvalidateRect
.
Se hd
è l'handle del dialogo, dunque, quel che occorre
fare per garantire che il grafico venga ridisegnato è
la chiamata:
WM_PAINT
arrivi a
destinazione, il che a sua volta causerà il WM_DRAWITEM
che ci interessa.
Se "avessimo fretta" (qui, non c'è ragione di averne), si
può rendere istantanea, anzichè differita, la "consegna"
del messaggio WM_PAINT
, e quindi la sua gestione, usando,
in qualsiasi momento dopo la chiamata a InvalidateRect
,
l'API BOOL UpdateWindow(HWND hWnd)
. Questa API
viene chiamata molto spesso (dai programmatori che non
hanno bene capito i meccanismi che la riguardano...), ma
in realtà è abbastanza raro che serva davvero
chiamarla (normalmente, il "giro" dei messaggi è già del
tutto soddisfacente, e le prestazioni sono
migliori, senza di essa).
Ci resta dunque solo da vedere: come raccogliere, e validare,
minx
, maxx
, miny
, maxy
,
auto-y
, e la funzione di cui fare il
grafico -- insomma, proprio il codice che ha a che vedere con
i controlli di edit, che, lo ricordiamo, sono poi il tema di questo
esercizio (ricordate Alice? questa, infatti, è una canzone su
Alice). Ce ne occuperemo, naturalmente, al prossimo capitolo.
Capitolo 31: disegno: fondamenti
Capitolo 33: i parametri del grafico
Elenco dei capitoli