Introduzione alle API Win32

Capitolo 16: un esempio (3)

Quel che vogliamo fare, nel nostro programmino d'esempio che abbiamo iniziato a definire agli scorsi capitoli 14 e 15, in risposta ai "clic" dell'utente sui quattro bottoni che abbiamo definito, è "spostare" una finestra -- il controllo di tipo static che pure abbiamo definito. Come ottenere questo?

Windows non definisce una API che serva veramente a "spostare" una finestra, cioè ad effettuare una modifica relativa alla sua attuale posizione. A prima vista, può sembrare che l'API MoveWindow serva allo scopo, ma, in realtà, nonostante il nome, esse non permette tali "spostamenti" -- solo il "settaggio" di una nuova posizione (e, sempre in barba al nome, anche il cambio di dimensione di una finestra).

Per usare MoveWindow allo scopo di fare cambiamenti relativi alla posizione di una finestra, dovremmo prima scoprire la sua attuale posizione (e dimensione) nelle coordinate usate da MoveWindow (che, per una finestra-figlia, come è un controllo, sono coordinate relative alla finestra-contenitore, cioè il dialogo); poi, calcolare la nuova posizione; infine, usare MoveWindow per imporre la nuova posizione (e dimensioni eguali alle precedenti).

Non esiste, però, una API che ci dia direttamente le coordinate e dimensioni di una finestra nel sistema di coordinate usate da MoveWindow...! Fortunatamente, ci sono vari approcci alternativi.

Il più semplice (e l'unico che esamineremo ora) consiste nell'uso di due API che, a differenza della "ingannevole" MoveWindow, esistono in effetti in ragionevole "coppia": GetWindowPlacement, che ci dà informazioni sul "piazzamento" di una finestra, e SetWindowPlacement, che ci permette di stabilire questo stesso "piazzamento", esattamente nelle stesse unità di misura, e coordinate, che la precedente GetWindowPlacement ci ha fornito.

Le API di Windows si dividono normalmente (con poche, benedette eccezioni:-) in due categorie: quelle che prendono un grande numero di parametri (la maggior parte dei quali non ci interessano in una buona parte dei casi!), e quelle che prendono come parametro l'indirizzo di una struttura grossa e complicata (la maggior parte dei cui campi non ci interessano in una buona parte dei casi!).

Quelle del secondo tipo, anche se la cosa può non essere immediatamente evidente, sono generalmente meno scomode, perchè una struttura può in genere essere "azzerata in massa" (ad esempio con l'API ZeroMemory, che in realtà è normalmente solo una comoda macro, definita in un header file standard di Windows, che riveste la funzione standard memset...), dopo di che possiamo settare i soli campi che interessano; la struttura può, alternativamente, essere riempita da una funzione che ci fornisce informazioni, e di essa useremo allora i soli campi che ci interessano; e così via. Le API che prendono un grande numero di parametri distinti non offrono analoghe comodità (un dettaglio che sarà bene tenere presente se mai ci si troverà nella posizione di progettare un insieme di API analogo per complessità e ricchezza a quello che offre Windows...:-).

GetWindowPlacement e SetWindowPlacement sono API di quelle che prendono (per indirizzo, cioè attraverso un puntatore) una struttura; specificamente, nel loro caso, una struttura del tipo WINDOWPLACEMENT (inoltre, entrambe hanno, come primo parametro, l'HWND della finestra che interessa trattare). La struttura WINDOWPLACEMENT è così dichiarata negli header file di Windows:

typedef struct _WINDOWPLACEMENT { UINT length; UINT flags; UINT showCmd; POINT ptMinPosition; POINT ptMaxPosition; RECT rcNormalPosition; } WINDOWPLACEMENT; È piuttosto comune, in questi header, che il nome della struttura sia un typedef di un "vero nome" che si ottiene appiccicando un _, o tag_, o struct_, o altro ancora, al nome-tipo che si usa sempre.

Altrettanto comune è che il primo campo di una struttura sia riservato per dire quanti byte occupa la struttura stessa. Questo serve perchè, in certi casi, future versioni di Windows potranno, nelle stesse API, accettare strutture più estese, per dare o ricevere più informazioni -- tuttavia, le nuove versioni di queste API, controllando questo primo campo, saranno in grado di determinare se gli è stata passata una struttura "del vecchio tipo", e comportarsi di conseguenza (non fornendo le info supplementari, ovvero, usando dei "default" ragionevoli per le info non fornite). Si otterrà così la compatibilità binaria con vecchi programmi già compilati, pur offrendo nuove funzionalità a programmi nuovi.

In altre parole, non è un brutto schema (un'altra buona idea da tenere presente se mai ci si troverà a progettare un analogo insieme di API). [Peccato naturalmente che, come tante altre buone idee di progetto presenti qua e là nelle API di Win32, anche questa non sia usata con totale coerenza: vale, sì, per molte API, ma altre invece accettano la "dimensione della struttura", non come parte di essa, ma come argomento separato, ed altre ancora ignorano la questione e non saranno dunque mai "upgradabili" in questo modo, vista la necessità che future version di Windows offrano compatibilità binaria con programmi scritti in precedenza...]

 

Per avere informazioni sul "piazzamento" di una finestra, dunque, la "forma idiomatica" è:

WINDOWPLACEMENT wp; ZeroMemory(&wp, sizeof(wp)); wp.length = sizeof(wp); if(!GetPlacement(hwnd, &wp)) { // errore -- diagnosticare, magari con // l'aiuto dell'API GetLastError() } // qui possiamo consultare i campi di wp In questo frammento di programma, una tantum e tanto per cambiare, abbiamo anche fatto il (doveroso!) il controllo di errore... le API non sono quasi mai funzioni "void", bensì ritornano una indicazione di errore di tipo BOOL (FALSE, cioè zero, in caso di errore), o, se devono tornare normalmente un "valore utile", hanno generalmente un "valore riservato" che viene tornato in caso d'errore (molto spesso il valore tornato in caso di errore è zero, ma non sempre; a volte, ad esempio, per alcune delle API che tornano una HANDLE, l'indicazione di errore è invece il ritorno di INVALID_HANDLE_VALUE). Se un errore si è verificato, il suo codice d'errore è (quasi) sempre reperibile chiamando l'API GetLastError, e su questo codice si può basare la segnalazione dell'errore all'utente, il tentativo di rimediare all'errore, o che altro.

Torneremo più avanti (brevemente) sull'argomento; per ora, noi daremo spesso per scontato che le nostre chiamate alle API abbiano successo (strategia che sarebbe disastrosa in un vero programma, ma che usiamo qui al solo scopo di alleggerire gli esempi e la relativa esposizione!).

Vediamo ora i campi della WINDOWPLACEMENT che GetWindowPlacement riempie per noi, e che quindi noi possiamo esaminare dopo averla chiamata con successo. length lo abbiamo assegnato noi all'inizio, e l'API non lo cambia; flags è sempre a zero. showCmd ci indica se la finestra sia ridotta a icona (minimizzata), forzata a riempire lo schermo (massimizzata), o in condizioni di visualizzazione normali; per una finestra-figlia, come il controllo che qui ci interessa, avremo sempre quest'ultima condizione. ptMinPosition e ptMaxPosition danno le coordinate usate se la finestra è rispettivamente iconizzata o massimizzata, e quindi, per la stessa ragione, in questo caso non ci interessano. Tutta l'info di nostro interesse in questo caso, dunque, è contenuta nel campo (di tipo RECT) rcNormalPosition.

RECT è una semplicissima struttura che serve a rappresentare un rettangolo (in questo caso, quello occupato sullo schermo dalla finestra sulla quale abbiamo chiesto informazioni quando è nelle sue normali condizioni di visualizzazione):

typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT; left e right sono le coordinate dei bordi sinistro e destro del rettangolo, top e bottom quelle dei suoi bordi superiore e inferiore.

La documentazione non è chiarissima su quali coordinate e unità di misura usi la struttura WINDOWPLACEMENT, ma, in pratica, si tratta di pixel, e, per finestre figlie, di coordinate entro l'area-cliente della finestra padre.

 

Torniamo, dunque, al nostro problema... Windows non ci offre proprio la funzione API che vorremmo:

BOOL WindMove(HWND hwnd, int dx, int dy) ma ci dà tutti gli strumenti per farcela noi, tutto sommato in modo abbastanza semplice: { WINDOWPLACEMENT wp; ZeroMemory(&wp, sizeof(wp)); wp.length = sizeof(wp); if(!GetWindowPlacement(hwnd, &wp)) return FALSE; wp.rcNormalPosition.left += dx; wp.rcNormalPosition.right += dx; wp.rcNormalPosition.top += dy; wp.rcNormalPosition.bottom += dy; return SetWindowPlacement(hwnd, &wp); }

 

Armati di questa funzione general purpose di nostra fattura, la gestione dei comandi nel nostro programmino d'esempio diventa allora semplice:

void OnDlgCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { const int dp = 5; // per esempio if(codeNotify != BN_CLICKED) return; HWND hStatic = GetDlgItem(hwnd, IDC_STICON); switch(id) { case IDC_BUTTON1: WindMove(hStatic,-dp,0); break; // sinistra case IDC_BUTTON2: WindMove(hStatic,+dp,0); break; // destra case IDC_BUTTON3: WindMove(hStatic,0,-dp); break; // alto case IDC_BUTTON4: WindMove(hStatic,0,+dp); break; // basso } } Possiamo finalmente rimettere assieme il programma, ricompilare, e vedere infine un qualche movimento!

Ci sono però ancora almeno un paio di dettagli, nel comportamento di questo minuscolo programma, che non possiamo considerare pienamente soddisfacenti... merita rifletterci un po', e ve ne lascerò tempo sino al prossimo capitolo!-)


Capitolo 15: un esempio (2)
Capitolo 17: alcune migliorie
Elenco dei capitoli