Introduzione alle API Win32

Capitolo 32: l'impostazione del grafico

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:

int imap(double d, double dmin, double dmax, int imin, int imax) { double rin = dmax-dmin; double rou = imax-imin; double res = (d-dmin)/rin*rou + imin; return (int)(0.5+res); }

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:

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...:

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:

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:

BOOL InvalidateRect(HWND hWnd, const RECT *lpRect, BOOL bErase)

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:

InvalidateRect(GetDlgItem(hd,IDC_GRAFICO),0,FALSE); e questo farà sì che, "a suo tempo" (quando non c'è nulla di più urgente da fare), il 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