Introduzione alle API Win32

Capitolo 17: alcune migliorie

Come accennavamo alla fine del capitolo precedente, la nostra piccola applicazione interattiva di esempio non è ancora del tutto soddisfacente.

Un primo problema emerge quando, nel far "girellare" l'icona sul dialogo, essa si trova a "interagire" con i bottoni che si trovano in alto a destra della dialog-box.

Anzitutto, vediamo l'icona passare "sopra" i bottoni, con l'eccezione di quello che stiamo cliccando, cui invece sembra passare "sotto"; peggio, quando "passa sotto", notiamo che essa sbuca "sporca" dall'altra parte, portando le tracce del bottone che, chiaramente, le è stato "disegnato sopra" (tracce transitorie, perchè se copriamo il dialogo con un'altra finestra e poi lo scopriamo di nuovo, esse non ci sono più -- ma, comunque, si tratta di un effetto grafico indesiderato e molto spiacevole!).

Quando delle "finestre-sorelle" interagiscono "graficamente" in questo modo, è di solito un chiaro sintomo che manca, fra i loro bit di stile, uno in particolare: WS_CLIPSIBLINGS.

In effetti, non abbiamo messo questo bit di stile nel nostro file .RC (e Windows non lo attiva di default perchè, di solito, le finestre dei controlli sono "ferme", rispetto al dialogo in cui si trovano; quindi, questo "clipping" non darebbe benefici, però potrebbe "costare" un sia pur minimo appesantimento del carico di esecuzione).

Per attivare questo bit di stile per ciascuna delle finestrelle dei nostri controlli, basta, nel file .RC, cambiare le righe fra il BEGIN ed END del dialogo a qualcosa come:

PUSHBUTTON "Button1",IDC_BUTTON1,130,30,20,20,BS_BITMAP|WS_CLIPSIBLINGS PUSHBUTTON "Button2",IDC_BUTTON2,150,30,20,20,BS_BITMAP|WS_CLIPSIBLINGS PUSHBUTTON "Button2",IDC_BUTTON3,140,10,20,20,BS_BITMAP|WS_CLIPSIBLINGS PUSHBUTTON "Button2",IDC_BUTTON4,140,50,20,20,BS_BITMAP|WS_CLIPSIBLINGS ICON "",IDC_STICON,68,58,20,20,WS_CLIPSIBLINGS Per la "icona" (STATIC, con implicito SS_ICON ecc), WS_CLIPSIBLINGS risulta l'unico bit di stile che viene esplicitamente settato; per i "pushbutton" (BUTTON, con implicito BS_PUSHBUTTON ecc), esso è "in aggiunta" al già asserito BS_BITMAP, e quindi usiamo l'operatore di "or, bit-per-bit" (barra verticale) così che entrambi i bit vengano settati.

Ricompilando questo RC, e rilinkando il tutto per aggiornare le risorse del nostro EXE, vediamo che, in effetti, la situazione è diventata molto più ragionevole; l'icona passa sistematicamente "sotto" tutti i bottoni (perchè, elencandola per ultima, e quindi facendo sì che venga creata per ultima, implichiamo che è "la più bassa" nell'ordine della "coordinata Z") e comunque non viene "sporcata" da questi passaggi. Se volessimo invece farla passare "sopra", basterebbe naturalmente elencarla, nel file .RC, prima dei bottoni.

Un altro potenziale difetto del nostro programma è che, a forza di clic sui bottoni, è possibile "spinger fuori" l'icona, in modo che essa non risulta più visibile sul dialogo. È discutibile se questo sia "giusto" o meno, ma più interessante può essere asserire che non lo è, e studiare come possiamo rimediare a questo "difetto".

Evidentemente, possiamo arricchire la funzione WindMove, che già ci siamo scritti, in modo che, facoltativamente, limiti lo spostamento che impartisce alla finestra che tratta, in modo da mantenerla all'interno di un certo rettangolo. Un buon modo per esprimere questo è con un classico idioma del C: aggiungiamo un parametro, di tipo puntatore a rettangolo (RECT*, o LPRECT che dir si voglia); se questo parametro è nullo, indica che non si desidera alcuna limitazione al movimento della finestra; se non nullo, punta al rettangolo entro il quale la finestra va mantenuta. Le modifiche necessarie alla nostra WindMove sono assai modeste:

Resta solo da decidere quale rettangolo passare alla WindMove nella nostra OnDlgCommand -- e, se passarle effettivamente l'indirizzo di un rettangolo, o, invece, un puntatore 0, in modo che essa non ponga alcun limite allo spostamento dell'icona.

Perchè non lasciare la scelta all'utente interattivo? Tutto sommato, basta aggiungere un bottone, di tipo "check box", per la scelta di "limitare il movimento dell'icona all'interno del dialogo" -- e questo si può fare semplicemente aggiungendo una AUTOCHECKBOX fra il BEGIN e l'END del dialogo (questo statement è la forma abbreviata del CONTROL con classe BUTTON e stile da check-box a risposta automatica) -- ricordiamo che "auto", qui, significa che il segno "spuntatura" verrà automaticamente messo o tolto nel box quando l'utente vi fa clic con il mouse, così che il nostro programma non deve preoccuparsene.

Nella OnDlgCommand, possiamo aggiungere una variabile di tipo LPRECT (puntatore a RECT), e una di tipo RECT:

LPRECT pRectLimit = 0; // default: niente limiti RECT rectLimit; Prima della istruzione di switch, entro la quale vi sono le chiamate a WindMove, dovremo verificare se è necessario imporre i limiti, chiedendo al nuovo auto-checkbox quale sia il suo stato: if(SendDlgItemMessage(hwnd, IDC_LIMITS, BM_GETCHECK, 0, 0)==BST_CHECKED) { // vanno accertati i limiti, poi: pRectLimit = &rectLimit; } else { pRectLimit = 0; } Inoltre, tutte le chiamate a WindMove nello switch dovranno avere pRectLimit come ultimo parametro.

Resta solo da capire come ottenere, nella variabile RECT di nome rectLimit, il rettangolo di limitazione del movimento, entro l'if che abbiamo appena scritto.

Qui ci torna comoda l'API (che vediamo per la prima volta)...:

BOOL GetClientRect(HWND hwnd, LPRECT prect) che deposita, nel RECT il cui indirizzo le è passato come secondo parametro, le coordinate della "area cliente" della finestra la cui handle riceve come primo parametro (attenzione, naturalmente, a non fare l'errore, tipico dei principianti assoluti del C!, di passare come secondo parametro una qualsiasi variabile di tipo LPRECT, supponendo che in qualche modo magico la funzione chiamata possa settarne il valore... i parametri, alle API come a tutte le altre funzioni, in C sono passati per valore: qui, come secondo parametro, va passato l'indirizzo di una nostra variabile, già esistente, di tipo RECT!).

Una semplice chiamata:

GetClientRect(hwnd, &rectLimit); sarà dunque sufficiente (ciò perchè, come già abbiamo accennato, il sistema di coordinate delle API di "window placement" è appunto, per una finestra-figlia, relativo all'area-cliente della finestra padre, cioè, qui, del dialogo).

 

Stiamo, chiaramente, lasciando come esercizio per il lettore quello di "mettere insieme" tutti gli elementi che stiamo offrendo, allo scopo di ricostruire l'intero programma. Una volta fatto ciò, si potrà tornare a compilarlo e linkarlo, e osservare gli effetti.

Una anomalia che rimane, e che pure lasciamo come esercizio da risolvere al lettore: se l'icona è "mezza fuori" dall'area cliente del dialogo, e l'utente clicca sulla checkbox per chiedere che i suoi spostamenti vengano limitati all'interno del dialogo, l'icona, lì per lì, non si sposta; al primo clic su uno dei bottoni con freccia, tuttavia, essa "salta" all'interno del dialogo.

Chiaramente, sarebbe meglio che "saltasse", invece, in risposta al clic stesso sulla checkbox. Suggerimento per la soluzione di questo problemino: basta gestire, come gli altri clic sui bottoni, anche questo; quali "spostamenti" andranno portati in questo caso alla nostra icona...?

 

Un altro problema, decisamente scocciante per l'utente interattivo di questo programmino, non ammette una soluzione così immediata... l'icona, come da specifiche, risponde ai "clic" sui bottoni-freccia, e va bene; ma se l'utente, come può venire spontaneo, tiene premuto il tasto sinistro del mouse, con il cursore su uno dei bottoni... non succede niente, sinchè, lasciando il tasto stesso, egli non "completa" l'azione di clic. Sarebbe bello, invece, che i bottoni stessi avessero una azione di, diciamo, "ripetizione automatica"; in altri termini, un simile comportamento da parte dell'utente dovrebbe avere come risultato, non la più assoluta immobilità dell'icona, bensì un suo continuo spostamento nella direzione indicata.

Come potremmo ottenere questo tipo di comportamento...? Il seguito, naturalmente, al prossimo capitolo.


Capitolo 16: un esempio (3)
Capitolo 18: timer e subclassing
Elenco dei capitoli