La varietà di funzioni che le API di Windows mettono a nostra disposizione per analizzare e modificare un device context (DC) [e, naturalmente, per disegnarci sopra], è spaventosamente vasta. Per fortuna, non avremo bisogno di altro che un manipolo di esse per il nostro attuale compito, che è in effetti abbastanza semplice: quello, ricordiamo, di disegnare su di un controllo static owner-drawn il grafico di una funzione.
Uno dei compiti del gestore di un messaggio
WM_DRAWITEM
è quello di "lasciare il DC come
lo si trova". Un device context ha svariati elementi
di "stato" (modificare questi elementi, che hanno
effetto su varie successive operazioni di disegno,
è più semplice e rapido che passare tutti i
parametri di stato a ciascuna di queste operazioni);
il gestore in questione può cambiarli, ma, se lo fa,
deve poi anche ripristinarli come li ha trovati.
Può essere scomodo tenere traccia di tutte le modifiche che si sono fatte; fortunatamente, due specifiche API ci risparmiano questo lavoro (a prezzo di un eventuale, ma comunque modesto, appesantimento del carico computazionale):
Per comodità ancora maggiore, se nSavedDC
è negativo, esso è preso come riferimento relativo;
RestoreDC(hdc,-1)
, ad esempio, ripristina lo
stato salvato dalla SaveDC(hdc)
più recente,
il che ci risparmia persino la modesta fatica di
salvarci il valore ritornato dalla SaveDC
stessa!
Se si ritiene opportuno modificare lo stato del
DC, è dunque opportuno, per semplicità,
avere una coppia di chiamate, SaveDC(hdc)
all'ingresso e RestoreDC(hdc,-1)
all'uscita,
per garantire di ripristinarlo correttamente.
Noi, in realtà, non ne avremo bisogno, ma è
una tecnica molto comoda (e ancora più se
incapsulata in un costruttore e un distruttore
di un oggetto locale in C++, naturalmente).
Fra gli elementi dello "stato" di un DC ci sono vari "oggetti grafici", e in particolare una "penna", che determina il "tratto" col quale curve e linee vengono disegnate, ed un "pennello", che determina il modo in cui vengono riempite le aree. Se i colori "normali" che presenta un DC non ci soddisfano, un modo di cambiarli è quello di cambiare penna e pennello correnti del DC stesso. Ancora una volta, è una tecnica che noi qui non useremo (ma il lettore è invitato a fare esperimenti con essa...!), ma è importante averla presente.
Cosa useremo, dunque...? Beh, ragioniamo su cosa dobbiamo fare... possiamo avere un colore di sfondo sul rettangolo del nostro static owner-drawn; e vogliamo tracciare su di esso una serie di linee (in realtà, una sola linea di molti segmenti, poichè ciascuno inizia dove il successivo finisce). Supponendo di accettare la penna di default (sottile, probabilmente nera), che è tutto sommato perfettamente ragionevole per un semplice grafico, possiamo mantenere la piccola ambizione di decidere il colore di sfondo.
C'è un'API comodissima per questo scopo...:
HBRUSH
("handle di un pennello"), e lei
fa tutto. Il DC e il RECT
di nostro interesse, come
ricorderete, li abbiamo già a mano -- sono i
campi hDC
e rcItem
della struttura, di tipo
DRAWITEMSTRUCT
, alla quale viene passato
un puntatore (in lParam
) assieme al messaggio
WM_DRAWITEM
che supponiamo di stare gestendo.
E un HBRUSH
, come possiamo ottenerlo...?
Anche qui, i modi sono tanti (ma tanti), ma, fra i più semplici, abbiamo la API...:
COLORREF
è la descrizione
del colore che desideriamo, e possiamo ottenerlo,
ad esempio, con la macro:
r
, g
, e b
, sono i valori, ciascuno fra
0 e 255,
delle componenti rossa, verde, e blu, del colore
di nostro interesse.
L'HBRUSH
così creato, naturalmente, è
poi nostra responsabilità gettarlo, chiamando su
di esso l'API:
HGDIOBJ
è una generica "handle ad
oggetto grafico" (di cui l'HBRUSH
è un caso
particolare).
Riassumendo, dunque, il codice, nel nostro
gestore di WM_DRAWITEM
, per riempire l'item
di un colore di sfondo desiderato, è davvero
semplice...:
Una volta tanto, abbiamo esplicitamente controllato che il pennello sia stato creato (ricordiamo che, di solito, per semplicità, omettiamo i vari controlli di errore nei nostri esempietti!); la creazione potrebbe risultare impossibile se, ad esempio, lo schermo sul quale stiamo disegnando non avesse a disposizione una sufficiente varietà di colori, nel qual caso, in questo esempio, scegliamo di lasciare semplicemente il rettangolo nel suo normale colore di fondo (la maggior parte degli ambienti Win32 supporta in modo implicito algoritmi, detti di "dithering", per "simulare" colori che non sono in realtà disponibili, ma non tutti tali ambienti hanno questa possibilità).
Per disegnare una linea a più segmenti, Windows ci offre una API altrettanto comoda e semplice:
POINT
(ciascuna, ricordiamo, con due coordinate
LONG
, x
e y
), e
Windows farà il resto, disegnando
sul DC i segmenti di retta che connettono questi punti
(il primo al secondo, il secondo al terzo, eccetera).
Ci rimane soltanto, dunque, il problema di come
preparare, e come mettere a disposizione della
funzione che gestisce WM_DRAWITEM
, questo
array di punti e la sua lunghezza. Per la seconda
metà, conosciamo già la soluzione: WM_DRAWITEM
riceve come argomento la HWND
del nostro
dialogo, quindi può recuperare informazioni che,
altrove nel nostro programma, abbiamo "connesso" al dialogo,
o come "window property", o, in modo marginalmente
più efficiente, con SetWindowLong
(l'indice DWL_USER
è riservato
appositamente per una longword determinata dalla
applicazione, cioè noi, e associata al dialogo); il
normale modus operandi è di porre in questo LONG
il cast del puntatore ad una struttura che contiene
tutte le informazioni d'interesse. Il codice completo
del gestore potrebbe dunque essere qualcosa come,
per una qualche opportuna struttura:
Qui abbiamo tenuto conto di varie possibilità, e
ipotizzato una struttura assai elegante e avanzata:
anzitutto, se il puntatore a stru
manca, usiamo
l'API TextOut
per informare l'utente che non siamo
ancora pronti per tracciare il grafico; se c'è,
supponiamo vi siano inclusi il colore di sfondo
(come campo di tipo COLORREF
), due campi
che danno l'array di punti e il numero di punti,
ed il puntatore a una funzione che, chiamata con
due argomenti (il puntatore alla struttura, che
conterrà tutti i campi che le servono, e quello
al rettangolo di disegno, dato geometrico assai
importante), torna un BOOL
-- vero se il grafico
è pronto per essere disegnato,
falso altrimenti (implicitamente, essa
provvederà a renderlo pronto, se possibile,
nel caso che ancora non lo fosse).
Questo è uno schema, non propriamente
object-oriented, ma almeno object-based, che
ci permette di incapsulare nel gestore del
messaggio WM_DRAWITEM
tutto e solo quello
che serve per disegnare, demandando invece
ogni altra decisione a un'altra funzione, trovata,
con massima flessibilità, grazie all'uso del
puntatore-a-funzione contenuto nella struttura.
Un vantaggio di questo approccio avanzato
è che il lettore può già mettere
insieme molto agevolmente tutto il resto del programma,
lasciando a zero la longword DWL_USER
del dialogo,
e verificare che appaia il giusto messaggio di "non
pronto"; mettendo nella struttura l'opportuno campo
cr
, si può anche verificare il comportamento con vari
colori di fondo.
Noi, naturalmente, continueremo a tratteggiare il resto della struttura di codice sottesa da questo (sempre nell'ipotesi semplificativa di usare i message cracker di WindowsX.h, naturalmente), ma... alla prossima puntata!
Capitolo 30: il disegno in Windows
Capitolo 32: l'impostazione del grafico
Elenco dei capitoli