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:
_
, 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" è:
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):
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:
Armati di questa funzione general purpose di nostra fattura, la gestione dei comandi nel nostro programmino d'esempio diventa allora semplice:
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