Introduzione alle API Win32

Capitolo 12: ID e HWND

Parlando di spedizione di messaggi, è il caso di evidenziare un'altra particolarità. I messaggi (almeno per quanto abbiamo visto sino ad ora!) si spediscono sempre ad una finestra, e richiedono, quindi, l'HWND di quella finestra. Tutto bene, dunque, se l'HWND della finestra ci viene resa disponibile nel contesto in cui vogliamo interagire con essa -- ad esempio, come abbiamo visto nel capitolo 6, quando, a fronte di un WM_COMMAND, vogliamo agire sulla finestra (controllo) che ce l'ha spedito, poichè la HWND di quel controllo è l'lParam del messaggio stesso.

Ma spesso, naturalmente, a fronte di eventi su di un certo controllo X, noi vorremo agire su di un diverso controllo Y; ad esempio, cambiare la scritta su di uno STATIC, a fronte di clic, non sullo static stesso, ma su di un certo BUTTON con stile pushbutton. Come si fa, allora, a recuperare l'HWND dello STATIC, quando nel messaggio WM_COMMAND ci arriva invece quella del BUTTON...?

Dello STATIC, e in generale dei nostri controlli, noi conosciamo a tempo di compilazione l'ID -- quel numerello che (attraverso, come abbiamo spiegato, un #define in resource.h) abbiamo associato al controllo. L'HWND, invece, cambia ad ogni diversa esecuzione del programma -- non c'è modo di determinarla a priori, una volta per tutte, mentre il programma viene compilato!

Windows, naturalmente, ci fornisce la funzionalità richiesta: risalire dall'ID di un controllo alla sua HWND (e viceversa, avendo una HWND, ottenere l'ID del controllo cui essa corrisponde).

Le API fondamentali per questo compito sono:

HWND GetDlgItem(HWND hDlg, // il dialogo int nIDDlgItem // ID del controllo ); torna l'HWND desiderata, o 0 se non esiste in quel dialogo un controllo con quell'ID; int GetDlgCtrlID( HWND hwndCtl // il controllo ); questa va "nel verso opposto", cioè recupera l'ID conoscendo l'HWND del controllo (non è valido chiamare questa API su di una HWND che non sia un controllo; in questo caso l'API dovrebbe tornare 0 -- purtroppo, questa segnalazione di errore non è garantita su tutte le versioni di Windows nè in tutti i casi).

Infine, l'API...:

HWND GetParent(HWND hWnd // il controllo ); ci permette, data l'handle di una finestra figlia, come ad esempio un controllo, di risalire a quella della finestra che la contiene (e questa API, affidabilmente, torna zero se non c'è nessuna "finestra-genitrice" per la HWND passatale come argomento).

Grazie alla presenza di queste API, non abbiamo bisogno di "registrarci" da nessuna parte queste fondamentali relazioni ID<->HWND<->genitore: possiamo sempre recuperarle dinamicamente a seconda delle esigenze. Nulla ci impedisce in effetti di "salvarci" da qualche parte una copia di queste info, ma non è strategia particolarmente consigliabile: il "costo" in termini di tempo per recuperarle è molto modesto, da bilanciare contro il "costo" in termini di spazio di salvarle da qualche parte (e in un sistema a memoria virtuale, dobbiamo ricordarlo, sprecare spazio significa alla fin fine sprecare tempo!); e soprattutto, per principio generale, è meglio non tenere copie duplicate e ridondanti di nessun tipo di informazioni, onde evitare errori di "disallineamento" fra le diverse "versioni" delle informazioni stesse.

(I lettori attenti di questo tutorial noteranno che tendo a dare una certa enfasi a questo principio della "non duplicazione delle informazioni" [la mia strategia consiste nel presentare ripetutamente questo principio, con una presentazione appena un poco diversa ogni volta -- non si tratta qui di una "duplicazione" quale, appunto, deploro, bensì di una applicazione del ben noto principio "repetita iuvant"!-]. Questa enfasi sul principio di "non duplicare" la pongo proprio perchè, come per tanti altri buoni principi generali di programmazione, è facile trovarsi a trascurarlo, per ignoranza, pigrizia, o distrazione, ma, quando lo si trascura, alla fine il prezzo si paga, eccome!, anche se non è sempre ovvio che lo si sta pagando.)

 

Ecco, dunque, un esempio di gestore di WM_COMMAND che interagisce con molteplici controlli: se viene cliccato il pushbutton di identificatore IDC_PRIMO, il gestore scrive, nello STATIC di identificatore IDC_SCRITTA, il testo "Primo" -- se invece viene cliccato quello di identificatore IDC_SECONDO, scrive, nello stesso STATIC, il testo "Secondo" (è lasciata come esercizio al lettore la preparazione del file .RC appropriato, e l'"incastro" nella dialog procedure del codice per determinare che questo è il gestore da eseguire). Come abbiamo accennato, usiamo due piccole comodità del C++: commenti con //, e dichiarazione delle variabili solo quando servono; per usare C puro, cambiare tutti i commenti alla forma /* ... */, e spostare tutte le dichiarazioni all'inizio della funzione.

BOOL TreControlli(HWND hDialogo, UINT nMessage, WPARAM wParam, LPARAM lParam) { // verifiche -- magari ridondanti -- che questo sia proprio // un caso che gestiamo: e` proprio un WM_COMMAND? if(nMessage != WM_COMMAND) return FALSE; // e` proprio il clic di un bottone? UINT codice = HIWORD(wParam); if(codice != BN_CLICKED) return FALSE; // e` proprio un bottone di nostro interesse? int idCliccato = LOWORD(wParam); if(idCliccato!=IDC_PRIMO && idCliccato!=IDC_SECONDO) return FALSE; // OK, il messaggio e` per noi -- gestiamolo! HWND hScritta = GetDlgItem(hDialogo,IDC_SCRITTA); if(!hScritta) return FALSE; // ulteriore controllo...! // scritta, e simultaneo "ultimo controllo d'errore": return SetWindowText(hScritta, idCliccato==IDC_PRIMO?"Primo":"Secondo"); }

Abbiamo qui usato la suaccennata funzione SetWindowText, invece che spedire un messaggio WM_SETTEXT, se non altro per la comodità di averne già un ritorno TRUE, o FALSE, direttamente usabile come risultato da ritornare dal nostro gestore.

 

Windows offre ulteriori funzioni API che possono tornar comode, benchè non siano certo indispensabili, per interagire con i controlli di un dialogo. Ad esempio, invece di trovare l'HWND con GetDlgItem, poi settare il testo con la SetWindowText, si può direttamente chiamare l'API:

BOOL SetDlgItemText(HWND hDlg, // il dialogo int nIDDlgItem, // ID del controllo LPCTSTR lpString // testo da impostare );

Il vantaggio è puramente uno di comodità, ma può valere la pena; le ultime tre istruzioni del nostro gestore diventerebbero dunque l'unica:

return SetDlgItemText(hDialogo, IDC_SCRITTA, idCliccato==IDC_PRIMO?"Primo":"Secondo");

Similmente abbiamo altre "API di comodità", come la GetDlgItemText, SendDlgItemMessage, eccetera; sono comodità, certo piccole, ma non del tutto trascurabili, quindi potremo farne uso.

Abbiamo anche "API di comodità" specializzate per certi tipi di controllo, come, per esempio:

BOOL CheckDlgButton(HWND hDlg, // il dialogo int nIDButton, // ID del bottone UINT uCheck // 0 o 1 (reset/set) ); equivalente a GetDlgItem seguito dalla SendMessage di un BM_SETCHECK, UINT IsDlgButtonChecked(HWND hDlg, // il dialogo int nIDButton // ID del bottone ); simile "rivestimento" del BM_GETCHECK, e l'API, che incapsula funzionalità assai maggiori: BOOL CheckRadioButton(HWND hDlg, // il dialogo int nIDFirstButton, // ID del 1o bottone del gruppo int nIDLastButton, // ID dell'ultimo bottone " " int nIDCheckButton // ID bottone da settare );

Quest'ultima equivale ad un ciclo sull'intero gruppo di radio-button, resettando tutti (senza pallino) con l'eccezione dell'unico scelto (cui il pallino viene invece messo).

L'ordine dei bottoni, nei cui termini determiniamo il "primo" e l'"ultimo" del gruppo, è, lo ricordiamo, l'ordine di creazione dei controlli entro il dialogo; usando il file .RC, esso coincide con l'ordine in cui ivi elenchiamo i bottoni fra le righe BEGIN e END del dialogo (per le funzionalità "autoradio", avremo normalmente anche il bit di stile WS_GROUP nel primo radio del gruppo, e, se questi radio button non sono gli ultimi del dialogo, sul primo del gruppo ad essi successivo).

Per "navigare", dal nostro codice, su questi "ordinamenti" e "raggruppamenti" (cosa che non occorre certo fare di frequente, ma solo per certe funzionalità avanzate), le API, di uso non del tutto semplice, sono GetNextDlgGroupItem e GetNextDlgTabItem -- non le approfondiremo ulteriormente, in quanto inappropriate al contesto di un semplice "tutorial" sulle API di Windows.


Capitolo 11: macro e API "di comodo"
Capitolo 13: message crackers
Elenco dei capitoli