Lezione 7 – Creazione di Finestre



By admin on settembre 12, 2010


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, come possiamo ottenere comunque un effetto "dinamico"? 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 che le finestre vengono pur create in un qualche modo. 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 pulsante, 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. Un bit di stile molto importante, che non avevamo ancora esaminato, è WS_CHILD. Se presente, si tratta di una "finestra figlia", come lo sono tutti i controlli.
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.
Per una finestra figlia, è obbligatorio specificare la HWND della finestra genitrice.
Il parametro hMenu, per una finestra-figlia, codifica, in realtà, l'importante valore "id del controllo"; questo parametro ha tale nome e tipo 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 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. Fortunatamente, per creare controlli di edit 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".
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 allocarci un buffer della giusta dimensione per GetWindowText, evitando il rischio di troncare il testo stesso.
Questo problema non si pone per il nome della classe; esso, infatti, è limitato a 255 caratteri, e potremo quindi usare un buffer di dimensione appropriata. 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; 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.
Passiamo subito a mostrare subito un possibile approccio alla ri-creazione di una finestra:

 
// ri-crea una window, eguale in tutto a quella
// passata; quest'ultima viene invece distrutta
HWND ricreaWindow(HWND hwnd, void* lpParam)
{
 // le classi non hanno mai nomi > 255 caratteri
 char className[256];
 GetClassName(hwnd, className, 256);

 // il testo della window ha lunghezza qualsiasi
 char *winText;
 int nChars = GetWindowTextLength(hwnd);
 // cast per compatibilità C++ (innocuo in C):
 winText = (char*)malloc(1+nChars);
 GetWindowText(hwnd, winText, 1+nChars);

 // stile, stile esteso, genitrice o proprietaria
 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
 DWORD dwExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
 HWND hwndParent = GetParent(hwnd);

 // geometria e menu-o-ID
 RECT r;
 GetWindowRect(hwnd, &r);

 HMENU hMenu;
 if(dwStyle & WS_CHILD)
   {
    // finestra-figlia: coordinate relative!
    MapWindowPoints(HWND_DESKTOP, hwndParent, (POINT*)&r, 2);
    hMenu = (HMENU)GetDlgCtrlID(hwnd);
   }
 else
   {
    // non-figlia: evitiamo incidenti sul menu
    hMenu = GetMenu(hwnd);
    SetMenu(hwnd, 0);
   }
// la HINSTANCE
HINSTANCE hInst = (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE);

// il font
WPARAM wFont = SendMessage(hwnd, WM_GETFONT, 0, 0);

// via la finestra vecchia!
DestroyWindow(hwnd);

// ed ecco la nuova...!
hwnd = CreateWindowEx(
        dwExStyle,
        className,
        winText,
        dwStyle,
        r.left, r.top, r.right-r.left, r.bottom-r.top,
        hwndParent,
        hMenu,
        hInst,
        lpParam);
// eventuale settaggio del font
 if(wFont) SendMessage(hwnd, WM_SETFONT, wFont, MAKELPARAM(TRUE, 0));
 return hwnd;
}

 
Alcuni dei dettagli ancora da chiarire sono piccoli; ad esempio, il messaggio WM_SETFONT prende l'HFONT come wParam, mentre l'lParam, come l'abbiamo costruito, dice alla finestra-bersaglio di "ridipingersi" subito per riflettere il "nuovo" font settato; la malloc, che non richiederebbe un cast per l'uso in C (ove void* può essere assegnato a qualsiasi puntatore) lo richiede invece in C++, quindi lo poniamo, per totale compatibilità -- minuzie.
Le cose più interessanti sono invece quelle di geometria. Anzitutto, è importante notare che r.right-r.left è la giusta espressione per dare la larghezza della finestra.
Ho anche infilato nel trattamento della geometria un'API che non avevo ancora nominato, la

MapWindowPoints(HWND hFrom, HWND hTo, POINT* pPoints, int nPoints);

 

che serve a "mappare" delle coordinate in pixel da una finestra all'altra, usando la pseudo-HWND HWND_DESKTOP per esprimere "coordinate di schermo". La GetWindowRect ci dà, appunto, sempre le coordinate di schermo, mentre, per creare una finestra-figlia, ci servono invece quelle relative alla finestra-genitrice; così "mappando", risolviamo il problema, approfittando anche del comodo fatto che un RECT è identico ad un array di due POINT (quello in alto a sinistra, poi quello "subito dopo" quello in basso a destra), anche se occorre un cast per convincere di ciò il nostro compilatore.
Avendo, dunque, a disposizione la nostra nuova funzione ricreaWindow, sarà facile modificare il nostro programma sperimentale: basta ricreare l'edit a ogni modifica al suo stile, oppure, volendo, avere un altro bottone separato, un pushbutton, che causi la ri-creazione, ovvero una checkbox che la renda facoltativa.





Lascia un Commento