C'eravamo lasciati, alla fine del capitolo precedente, con un piccolo dilemma: se certi bit di stile (o, per generalizzare, altre caratteristiche di una finestra) vengono presi in esame solo alla creazione della finestra (in altre parole, i cambiamenti impartiti a queste caratteristiche non sono dinamicamente esaminati dalla window procedure predefinita della finestra stessa, certo per ragioni di efficienza), come possiamo ottenere comunque un effetto "dinamico", "come se" questi cambiamenti "agissero" appena ci torna comodo che agiscano...?
Questo "dilemma" è in realtà un semplice sillogismo...:
Abbiamo suggerito, infatti, che la soluzione avrebbe
coinvolto un'API, che non abbiamo ancora esaminato,
ma la cui esistenza in una qualche forma è chiara e
implicita nelle operazioni di Windows già viste
sino a questo punto: avevamo
appunto in mente l'API di creazione di finestre. Sino
ad ora, non ne avevamo avuto bisogno,
perchè le nostre finestre sono sempre state create
implicitamente, per nostro conto, da altre API (in
particolare, dall'API DialogBoxParam
, che crea, non
solo la finestra del dialogo stesso, ma implicitamente
anche tutte le finestre dei controlli descritti nel "dialog
template" che produciamo dal file .RC); ma era chiaro,
sotto sotto, che le finestre vengono pur create in un
qualche modo, e, visto che adesso, come ci mostra il
nostro sillogismo, dobbiamo appunto occuparci in modo
più diretto di creazione di finestre, è giunto il momento
di studiare come questa si compia.
Il modo più generale di creare una finestra è l'API:
GetWindowText
e SetWindowText
-- lo "stile", beh, è
proprio la ragione principale per cui in questo momento dobbiamo
studiare questa API!-).
Altre caratteristiche, come lo "stile esteso", non le abbiamo
ancora prese in considerazione, ma, tutto sommato, quello
che qui ci serve è solo creare una finestra che ne imiti
strettamente un'altra, e quindi ci basta poter recuperare i
valori delle caratteristiche in questione per questa "altra"
finestra, per passarli pari pari alla creazione della "nuova"
imitazione di essa; quindi, sapendo che GetWindowLong
ha
un indice GWL_EXSTYLE
che accede allo "stile esteso", non
ci serve, per ora, approfondire ulteriormente (non che sia
nulla di misterioso: sono solo altri nuovi bit di stile, che non
c'era modo di ficcare nell'unica longword che codifica lo
"stile" basilare!).
Un bit di stile molto importante, che non avevamo ancora
esaminato, è WS_CHILD
. Se presente, si tratta di una
"finestra figlia", come sono ad esempio tutti i controlli: vive
"entro" un'altra finestra "genitrice" (per un controllo, ciò
significa, di solito, il dialogo che lo contiene); se assente,
è una finestra "indipendente" (ci sono dei "distinguo", ma,
grosso modo, questa distinzione regge -- l'approfondiremo
più avanti), come è ad esempio un dialogo.
Le coordinate che definiscono la geometria di una finestra vanno date, sempre in pixel, rispetto al genitore per una finestra figlia, rispetto all'intero schermo per una non-figlia. (In questo senso, si può considerare che le finestre non-figlie siano, per così dire, "figlie" dell'"intero schermo".)
Per una finestra figlia, inoltre, è obbligatorio specificare
la HWND
della finestra genitrice (facoltativamente si può
farlo anche per una non-figlia, ma in questo caso non si
tratta, strettamente parlando, di una "genitrice", bensì
di una "finestra-proprietaria", concetto un poco più sottile
che approfondiremo più avanti).
Il parametro hMenu
, per una finestra-figlia, codifica, in
realtà, l'importante valore "id del controllo"; questo parametro
ha il nome e il tipo
che si ritrova, perchè, per una window non-figlia, esso serve in
effetti a passare un "menu", concetto che per il momento
non approfondiremo ulteriormente.
La HINSTANCE
è la solita handle, che abbiamo già
abilmente ignorato parlando di WinMain
, e continueremo
per ora a ignorare risolutamente, a parte la nota che
la HINSTANCE
di una finestra si può recuperare con
la solita GetWindowLong
, indice GWL_HINSTANCE
.
L'ultimo parametro è il più sottile, poichè il suo
significato (e se abbia un significato, o nessuno!)
dipende interamente dalla window-procedure della classe
di cui stiamo creando una finestra; può puntare a
dati di qualsiasi tipo, o a nulla di particolare (abbiamo
già visto una traccia di esso nell'ultimo parametro
dell'API DialogBoxParam
). Fortunatamente, per creare
controlli di edit (che è quel che qui ci serve), esso è
irrilevante (meglio passare dunque zero per esso); per
generalità, la nostra funzione "ricrea finestra" dovrà
però accettarlo come parametro -- l'unico ad essa
necessario, accanto alla HWND
da "ri-creare".
Certo, perchè (c'era bisogno di dirlo...?), visto che questa di "ri-creare una finestra" è una funzionalità chiaramente molto generale, la impacchetteremo in una funzione tutta sua! E perchè no, d'altronde? A un modestissimo costo (mettere appena un pochino più di generalità di quanta non ce ne serva), avremo un potente "mattone", che ci farà certo comodo nella futura costruzione di strutture più complesse... si deve sempre ragionare in termini di riuso di codice già esistente, e futura riusabilità del codice nuovo che stiamo scrivendo; il costo è modesto, i benefici sono grandi. Il programmatore che, con il passar del tempo, non accumula una piccola (o, non tanto piccola) "libreria privata" di funzioni comode e riusabili, sta sprecando una parte del suo tempo, per quanto successo abbiano gli specifici programmi che sta scrivendo al momento...!
Per potere estrarre dalla finestra esistente tutte le
informazioni che ci servono per ricrearla, dobbiamo
però esaminare anche un altro paio di API.
int GetWindowTextLength(HWND hWnd)
ritorna la
lunghezza del testo di una finestra; con essa,
potremo (ad esempio, con malloc
) allocarci un
buffer della giusta dimensione per GetWindowText
,
evitando il rischio di troncare il testo stesso (non
dobbiamo naturalmente scordarci di allocare un
carattere in più per lo 0
di terminazione, nè di
liberare, con free
se l'abbiamo allocato con malloc
,
il buffer una volta finito di usarlo!).
Questo problema non ci si pone per il nome della classe; esso, infatti, a differenza del testo di una finestra, è limitato a 255 caratteri, e potremo quindi usare un buffer di dimensione appropriata pur non disponendo di API per interrogare sulla lunghezza del nome della classe. Il nome della classe di una finestra si ottiene con l'API:
hMenu
per
finestre non-figlie, dovremo usare l'API:
HMENU GetMenu(HWND hWnd)
per ottenere il
"menu" (qualsiasi cosa sia -- per ora non ce ne
interessiamo!) della finestra precedente, ma anche
la SetMenu(HWND hWnd, HMENU hMenu)
, con
il secondo parametro a 0
, per
"togliere" quel menu alla finestra precedente,
altrimenti, quando sarà distrutta, distruggerà
anche il suo menu, e non potremmo dunque più
apporlo alla nuova "copia". Le finestre figlie,
col loro "control ID" che è solo un intero, non
hanno, naturalmente, questo problema.
Un dato che non appare fra i pur numerosi
parametri di CreateWindowEx
è il font
usato dalla finestra. Cosa sia un font, grosso
modo, spero che il lettore lo sappia (un insieme
di specifiche relative all'aspetto dei caratteri
sullo schermo); per ora, lo vedremo solo come
una handle, HFONT
. Non c'è una specifica
API relativa al font di una finestra, ma il
messaggio WM_GETFONT
ritorna la sua HFONT
(o 0
per dire "il font di sistema"), mentre il
messaggio WM_SETFONT
lo imposta, per
quelle finestre che supportano font, come ad
esempio i dialoghi ed i controlli. Dovremo
dunque, se occorre, inviare questo messaggio
subito dopo la creazione della finestra, perchè
il font risulti correttamente impostato.
Abbiamo dunque definito il modo di trovare
tutti gli elementi di una window che sono
necessari per ri-crearla eguale
con CreateWindowEx
, o per
re-impostare gli stessi valori subito dopo,
nel caso specifico del font. Invitiamo
caldamente il lettore, che voglia veramente
imparare qualcosa da questo tutorial, a
svolgere l'esercizio che consiste nel
raccogliere tutti questi elementi in una
funzione che svolga l'intero compito. Al
prossimo capitolo, che suggeriamo di leggere
solo dopo avere portato a termine
questo semplice esercizio, gli daremo
l'opportunità di confrontare la sua soluzione
con quella, certo simile, che proporremo noi.
Capitolo 23: un piccolo esperimento
Capitolo 25: ri-creare una finestra
Elenco dei capitoli