Introduzione alle API Win32

Capitolo 24: creazione di finestre

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

  1. i valori sono esaminati solo alla creazione della finestra,
  2. vogliamo che i valori vengano esaminati,
  3. ERGO, dobbiamo causare la creazione della finestra!

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:

HWND // ritorna l'HWND della finestra creata CreateWindowEx(DWORD dwExStyle, // "stile esteso" LPCTSTR lpClassName, // nome della classe di finestra LPCTSTR lpWindowName, // testo della finestra DWORD dwStyle, // stile della finestra int x, int y, int nWidth, int nHeight, // geometria HWND hWndParent, // finestra-genitrice HMENU hMenu, // menu, o identificatore di controllo HINSTANCE hInstance, // "istanza" del creatore LPVOID lpParam // puntatore a "dati supplementari" ); I tanti parametri descrivono quasi tutte le caratteristiche importanti della finestra creata. Molte di queste ci sono già del tutto comprensibili sulla base di quanto visto sinora; il "nome della classe", ad esempio, sarà "Button" per un bottone, e così via -- il "testo della finestra" è lo stesso che si accede e manipola con 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:

int GetClassName(HWND hWnd, LPTSTR lpClassName, // buffer per il nome int nMaxCount // quanto spazio nel buffer ); Per usare correttamente il parametro 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