Win32 Assembly

Capitolo 7: Menu, icone, cursori, bitmap e dialog.

Per rendere piu' intuitiva l'interazione tra l'utente e il computer, le interfacce grafiche come quella di Windows mettono a disposizione una serie di oggetti che vengono chiamati risorse; tra le risorse piu' conosciute si possono citare i Menu, le Icone, i Cursori e le immagini in formato BitMap (mappa di bit).

Attraverso i menu l'utente ha a disposizione una serie di scelte grazie alle quali puo' selezionare svariate funzionalita' dell'applicazione che sta utilizzando; nel caso ad esempio di un editor di testo, i menu vengono largamente utilizzati per permettere all'utente di richiedere l'apertura o il salvataggio di un file, la ricerca di una parola chiave, le operazioni di copia/incolla, la richiesta di aiuto (help), etc.

Le icone forniscono una rappresentazione visuale dei files e delle cartelle che si trovano nell'hard disk; grazie alle icone e' possibile distinguere i files dalle cartelle, e spesso e' anche possibile individuare il tipo di file con cui si ha a che fare (file di testo, file eseguibile, file batch, documento di Word, foglio di calcolo di Excell, etc).

I cursori permettono di assegnare qualsiasi forma (shape) al puntatore del mouse; in Windows l'utente ha a disposizione numerosi cursori predefiniti, con la possibilita' di creare anche dei cursori personalizzati. Attraverso la modifica della forma del cursore del mouse, e' possibile fornire precise informazioni all'utente; nel caso ad esempio di un programma di elaborazione grafica (come Paint), il cursore del mouse cambia forma a seconda dello strumento di disegno selezionato (penna, pennello, gomma da cancellare, spray, etc). A partire da Windows 95 vengono supportati anche i cursori animati; un cursore animato e' formato da una sequenza di immagini che possono essere paragonate ai fotogrammi di una pellicola cinematografica. Appena si carica un cursore animato, vengono fatti scorrere automaticamente i vari fotogrammi che danno cosi' l'idea di una animazione; nel seguito del capitolo vedremo come si deve procedere per dotare di cursori animati le proprie applicazioni.

Le bitmap sono immagini grafiche memorizzate su disco sotto forma di mappe di bit; in Windows il formato grafico predefinito e' rappresentato proprio dalle bitmap che vengono infatti utilizzate anche per memorizzare le icone e i cursori. Per migliorare le prestazioni, Windows utilizza spesso bitmap prive di qualsiasi forma di compressione grafica; rispetto ai formati grafici compressi come JPG, GIF, etc, lo svantaggio di una bitmap non compressa e' rappresentato dalle notevoli richieste di spazio su disco. Le icone e i cursori non sono altro che bitmap di forma quadrata; nel caso generale invece una bitmap e' una immagine formata da una superficie rettangolare di pixel.

Gli aspetti relativi alla struttura interna delle risorse di Windows vengono discussi in altri capitoli; in questo capitolo invece viene illustrato un procedimento che permette di incorporare in modo molto semplice svariate risorse nei propri programmi. Viene anche illustrato un modo per creare rapidamente una semplice finestra di dialogo (Dialog Box); le dialog box vengono largamente utilizzate per permettere all'utente di interagire (dialogare) con una applicazione.
Per mettere in pratica i concetti esposti in questo capitolo, viene presentato un programma di esempio chiamato RISORSE.ASM, disponibile sia in versione MASM che TASM; il codice sorgente dell'esempio si trova nei seguenti files zippati:

Download di tutti gli esempi del capitolo (versione MASM)
Download di tutti gli esempi del capitolo (versione TASM)

Ciascuno di questi file deve essere decompresso nella cartella di lavoro; gli esempi presentati nella sezione Win32 Assembly presuppongono che la cartella di lavoro sia:
c:\masm32\win32asm per il MASM e:
c:\tasm\win32asm per il TASM.
Dalla decompressione del file cap07masm.zip o cap07tasm.zip si ottiene una cartella Risorse che verra' quindi posizionata in:
c:\masm32\win32asm\Risorse per il MASM e:
c:\tasm\win32asm\Risorse per il TASM.
All'interno della cartella Risorse sono presenti ben 15 files, la maggior parte dei quali contengono le risorse utilizzate dal programma di esempio.
Se non si utilizzano i percorsi specificati in precedenza per le cartelle, e' necessario apportare le opportune modifiche ai percorsi indicati nei files RISORSE.ASM, RISORSE.RC, RISORSE.MAK (per il TASM) e RISORSE.BAT (per il MASM).

7.1 Il Resource File.

Il metodo piu' semplice e piu' rapido per incorporare numerose risorse nei propri programmi, consiste nel servirsi di appositi files che vengono chiamati Resource Files (files di risorse); il Resource File e' un file rigorosamente in formato ASCII, che attraverso una sorta di pseudo linguaggio di programmazione ci permette di dichiarare le caratteristiche di tutte le risorse da incorporare in un programma.
Convenzionalmente per i Resource Files si utilizza l'estensione predefinita RC; nel caso del nostro esempio il file di risorse si chiama RISORSE.RC. Apriamo quindi il file RISORSE.RC con il nostro editor preferito, ed esploriamone il contenuto; proprio all'inizio del file RISORSE.RC, troviamo la seguente istruzione:
#include "..\include\resource.h"
Come si puo' notare, questa e' chiaramente una istruzione del linguaggio C; la direttiva #include del C equivale ovviamente alla direttiva INCLUDE dell'Assembly. La caratteristica fondamentale dei files di risorse consiste proprio nel fatto che al loro interno dobbiamo utilizzare una sintassi presa in prestito da linguaggi come il C e il Pascal; in questo modo si ottiene una sorta di pseudo linguaggio attraverso il quale si possono codificare le risorse da inserire in un programma.
Il lavoro che con questo pseudo linguaggio viene svolto all'interno di un file di risorse, e' molto simile alla dichiarazione di una serie di macro alfanumeriche, ciascuna delle quali rappresenta una risorsa; le vare costanti predefinite come WS_CHILD, WS_VISIBLE, etc, che si utilizzano nel corpo di queste macro, vengono dichiarate all'interno dell'include file principale windows.inc (o win32.inc). Il Resource File richiede pero' che queste costanti vengano dichiarate con la classica sintassi del C, per cui e' necessario disporre di una copia in versione C di windows.inc; il MASM fornisce un apposito header file chiamato appunto resource.h, che si trova nella cartella INCLUDE, e siccome la sintassi del C e' standard, il file resource.h puo' essere utilizzato senza nessuna modifica anche con TASM. Il contenuto di questo file viene continuamente aggiornato dalla Microsoft man mano che escono nuove versioni di Windows; e' importante quindi procurarsi la versione piu' aggiornata possibile di resource.h. Si consiglia di dare un'occhiata a questo file per prendere confidenza con le numerose costanti simboliche presenti al suo interno.

Nel linguaggio C tutte le stringhe incluse tra /* e */ come ad esempio:
/* codici per i menu */
rappresentano un commento che puo' svilupparsi anche su piu' linee; si tratta quindi della stessa situazione che in Assembly si ottiene con la direttiva COMMENT. Se il commento si sviluppa su una sola linea, possiamo anche utilizzare la sintassi del C++ per i commenti scrivendo:
// codici per i menu

Proseguendo nella esplorazione del file RISORSE.RC, possiamo notare che dopo la direttiva #include incontriamo una numerosa serie di istruzioni del tipo:
#define CM_MENU1a 101
Anche in questo caso abbiamo a che fare con istruzioni scritte in linguaggio C; la direttiva #define del C equivale alla direttiva MACRO dell'Assembly, e ci permette quindi di dichiarare macro alfanumeriche distribuite su una o piu' linee. Nel nostro caso dobbiamo dichiarare una serie di semplici costanti numeriche, per cui la direttiva precedente corrisponde in Assembly a:
CM_MENU1a = 101
Lo scopo di tutte queste costanti numeriche viene chiarito piu' avanti.
Dopo le direttive #define incontriamo la dichiarazione di una risorsa che rappresenta il menu associato alla main window del nostro programma; la dichiarazione della struttura di un menu inizia con il nome simbolico assegnato a questa risorsa, seguito dalla parola chiave MENU. Nel nostro caso si ha:
RisorseMenu MENU
In pratica, la main window del nostro programma utilizza una risorsa menu chiamata simbolicamente RisorseMenu; al posto della stringa RisorseMenu e' possibile specificare un valore numerico. Possiamo scrivere ad esempio:
3500 MENU
Il codice numerico deve essere compreso tra 0 e 65535; come vedremo in seguito, l'utilizzo di un codice numerico al posto di una stringa ci permette di risparmiare diversi byte. Per motivi di chiarezza e' sempre meglio utilizzare nomi simbolici per identificare un valore numerico esplicito; nel caso della dichiarazione precedente possiamo scrivere ad esempio:
#define ID_MENU 3500
Una volta che abbiamo dichiarato questa costante simbolica possiamo scrivere:
ID_MENU MENU
Seguendo sempre la classica sintassi del C, si puo' notare che il corpo della risorsa menu inizia con una parentesi grafa aperta { e termina con una parentesi grafa chiusa }; se questi due simboli non sono presenti sulla vostra tastiera, li potete ottenere ugualmente ricordando che il codice ASCII di '{' e' 123, e il codice ASCII di '}' e' 125. Per ottenere la parentesi grafa aperta bisogna quindi tenere premuto il tasto [Alt] sinistro e premere in sequenza i tasti [1], [2] e [3] sul tastierino numerico; per ottenere la parentesi grafa chiusa bisogna invece tenere premuto il tasto [Alt] sinistro e premere in sequenza i tasti [1], [2] e [5] sul tastierino numerico. In alternativa, e' possibile utilizzare la parola chiave BEGIN al posto di {, e la parola chiave END al posto di }; in questo modo si ottiene una sintassi simile a quella del Pascal.
Subito dopo l'intestazione di RisorseMenu, troviamo l'elenco dei vari popup associati al menu; con il termine popup si indica la tipica finestra a discesa che compare quando si clicca con il mouse sul menu di un programma (spesso si utilizza anche la definizione di menu a tendina). Ciascun popup associato al menu viene specificato con una dichiarazione del tipo
POPUP "&Bitmap"
In questo modo, la stringa "Bitmap" comparira' nella menu bar (barra del menu) del nostro programma; come si puo' notare, la stringa associata ad ogni popup deve essere inserita tra doppi apici. Il simbolo '&' posto prima della B indica che questo popup puo' essere selezionato non solo con il mouse ma anche con la sequenza di tasti [Alt][B] (si tratta sempre del tasto [Alt] sinistro); il tasto da premere insieme a [Alt] per selezionare un popup viene chiamato hot key (tasto caldo).
Ciascun popup e' formato da una serie di menu items; con il termine menu item si indica una delle varie opzioni (o voci) che si possono selezionare all'interno di un popup. I vari menu items possono essere creati con dichiarazioni del tipo:
MENUITEM "&Penna", 304
In questo modo all'interno del popup "Cursori" comparira' l'opzione "Penna" con hot key [P]; per selezionare un hot key all'interno di un popup non e' necessario premere [Alt]. Il valore 304 specificato nella precedente dichiarazione, rappresenta il codice numerico che identifica il menu item "Penna"; anche in questo caso, i vari codici numerici devono essere compresi tra 0 e 65535. Ogni volta che selezioniamo un menu item, la window procedure del nostro programma riceve da Windows un messaggio WM_COMMAND accompagnato dal codice numerico che abbiamo assegnato allo stesso menu item; intercettando questo messaggio possiamo quindi rispondere all'azione richiesta dall'utente attraverso il menu.
Come e' stato ampiamente spiegato nei precedenti capitoli, l'utilizzo dei numeri espliciti nei programmi aumenta notevolmente il rischio di commettere errori; se nel codice sorgente del nostro programma inseriamo un numero sbagliato, non possiamo certo pretendere che l'assembler ci segnali l'errore che abbiamo commesso. Per questo motivo, e anche per ragioni di stile, si consiglia vivamente di utlizzare sempre costanti simboliche per gestire dei numeri espliciti; le numerose direttive #define presenti nel file RISORSE.RC hanno proprio lo scopo di dichiarare i codici numerici associati ai vari menu items del menu RisorseMenu. In questo modo possiamo scrivere ad esempio:
MENUITEM "&Penna", CM_CURSOR4
Osservando il popup "Font", possiamo notare che tra i vari menu items e' presente anche un popup "Altri Font"; in questo modo possiamo dichiarare dei popup innestati all'interno di altri popup. Osserviamo inoltre che anche la sequenza dei vari menu items associati ad ogni popup deve essere racchiusa dalla solita coppia { }; alternativamente, anche in questo caso possiamo utilizzare le parole chiave BEGIN e END.
Una considerazione finale sui menu riguarda il fatto che come si puo' notare dalle #define presenti nel file RISORSE.RC, ad ogni popup viene assegnato un differente gruppo di codici numerici, e all'interno di ogni popup i vari codici numerici sono consecutivi; in questo modo e' possibile apportare facilmente eventuali modifiche ad un popup, e si possono anche ottenere notevoli vantaggi in fase di elaborazione del messaggio WM_COMMAND.

Dopo la dichiarazione del menu RisorseMenu, nel file RISORSE.RC incontriamo una serie di altre dichiarazioni relative alle icone, ai cursori e alle bitmap che verranno utilizzate nel programma. Per incorporare una qualsiasi icona, dobbiamo utilizzare una dichiarazione del tipo:
NomeSimbolico ICON "nomefile.ico"
In pratica, questa dichiarazione e' formata dal nome simbolico che intendiamo assegnare alla risorsa, seguito dalla parola chiave ICON e dal nome del file contenente la bitmap dell'icona (se il file si trova in un'altra cartella, bisogna specificare il percorso completo); nel nostro caso vogliamo incorporare un'icona memorizzata nel file RISORSE.ICO, e vogliamo assegnare a questa risorsa il nome simbolico RisorseIcon. Dobbiamo scrivere quindi:
RisorseIcon ICON "risorse.ico"
Come vedremo in seguito, questa risorsa verra' utilizzata come icona personalizzata del programma RISORSE.EXE.
Le icone utilizzate da Windows sono delle bitmap quadrate formate da 16x16, 24x24, 32x32 o 48x48 pixel; un file contenente un'icona per Windows viene memorizzato su disco con estensione predefinita ICO, e contiene una struttura formata da un header (intestazione) seguito dalla codifica della bitmap vera e propria. L'icona associata ad una applicazione deve essere in formato 32x32, mentre l'icona piccola che verra' visualizzata nella barra del titolo (in alto a sinistra) deve essere in formato 16x16. L'icona associata ad una applicazione verra' utilizzata da Windows per rappresentare graficamente l'applicazione stessa nel File Manager; nel caso del nostro esempio vedremo che l'icona RISORSE.ICO verra' associata nel File Manager al programma RISORSE.EXE.
Se vogliamo creare icone personalizzate, dobbiamo procurarci un apposito editor di icone; a tale proposito possiamo effettuare una ricerca su Internet tenendo presente che l'editor che ci serve deve essere in grado ovviamente di gestire il formato specifico ICO utilizzato da Windows. Le icone sono immagini molto piccole, per cui sarebbe inutile crearle utilizzando un numero elevato di colori; per risparmiare spazio su disco e in memoria, e' vivamente consigliabile l'utilizzo di una palette (tavolozza) a 16 colori.

Dopo la dichiarazione per l'icona RisorseIcon, incontriamo una serie di altre dichiarazioni che elencano i vari cursori che verranno utilizzati dal nostro programma di esempio; come si puo' notare, il procedimento da seguire e' del tutto simile al caso delle icone. In pratica, dobbiamo indicare il nome simbolico da assegnare al cursore, seguito dalla parola chiave CURSOR e dal nome del file contenente la risorsa; nel nostro caso possiamo notare la presenza di dichiarazioni del tipo:
RisorseCursor1 CURSOR "jet.cur"
I cursori utilizzati da Windows sono delle bitmap quadrate formate da 32x32 pixel; un file contenente un cursore per Windows viene memorizzato su disco con estensione predefinita CUR, e contiene una struttura formata da un header seguito dalla codifica della bitmap vera e propria. I files contenenti cursori animati vengono invece memorizzati su disco con estensione predefinita ANI.
Se vogliamo creare cursori personalizzati, dobbiamo procurarci un apposito editor di cursori; in genere, molti editor di icone sono in grado di gestire anche il formato CUR per Windows, e gli editor piu' sofisticati supportano anche i cursori animati. In fase di creazione di un cursore personalizzato, e' necessario specificare una informazione importantissima, rappresentata dalle coordinate X e Y del cosiddetto hot spot (punto caldo); l'hot spot di un cursore e' il punto della bitmap del cursore, rispetto al quale Windows calcolera' istante per istante le coordinate del mouse. Nel caso dell'applicazione RISORSE.EXE, viene utilizzato ad esempio un cursore a forma di penna; in un caso del genere, il punto piu' ovvio da utilizzare come hot spot e' rappresentato naturalmente dalle coordinate della punta della penna.

Il Resource File puo' essere utilizzato anche per incorporare in una applicazione generiche immagini bitmap; a tale proposito dobbiamo specificare come al solito il nome simbolico assegnato alla risorsa, seguito dalla parola chiave BITMAP e dal nome del file contenente la risorsa stessa. Nel caso del file RISORSE.RC notiamo ad esempio la presenza di una dichiarazione del tipo:
RisorseLogo BITMAP "ramlogo.bmp"
Una bitmap generica e' una immagine formata da un rettangolo di pixel; un file contenente una immagine bitmap compatibile con Windows, viene memorizzato su disco con estensione predefinita BMP, e contiene una struttura formata da un header seguito dalla codifica della bitmap vera e propria. Per realizzare immagini bitmap si puo' utilizzare un qualsiasi programma di grafica come ad esempio Paint; al momento di salvare l'immagine su disco, bisogna richiedere il salvataggio in formato BMP per Windows.
Naturalmente il Resource File deve essere utilizzato per incorporare in un programma piccole immagini bitmap; si tenga presente infatti che tutte le risorse presenti in un Resource File vengono inserite direttamente nel file eseguibile finale, aumentandone notevolmente le dimensioni. Non avrebbe nessun senso quindi dichiarare in un Resource File un'immagine bitmap formata da 1024x768 pixel, con colori a 24 bit; in un caso del genere la strada da seguire consiste nel lasciare la bitmap in un file separato e caricarla poi in fase di esecuzione del programma.

Il file RISORSE.RC termina con la dichiarazione di una dialog box che verra' utilizzata come finestra di About (informazioni) del programma; tutti gli aspetti relativi a questa dialog box vengono discussi alla fine del capitolo.

Come e' stato detto in precedenza, qualsiasi risorsa definita in un Resource File puo' essere identificata non solo attraverso una stringa, ma anche attraverso un codice numerico compreso tra 0 e 65535; consideriamo ad esempio il cursore che nel file RISORSE.RC viene identificato con la stringa RisorseCursor2. Se vogliamo identificare questo cursore attraverso il codice numerico 4002, possiamo dichiarare la seguente costante numerica:
#define ID_CURSOR2 4002
A questo punto possiamo scrivere:
ID_CURSOR2 CURSOR "stella.cur"

Il file RISORSE.RC una volta completato viene passato ad un apposito compilatore chiamato Resource Compiler (compilatore di risorse); nel caso del MASM il compilatore di risorse si chiama RC.EXE, e come al solito si trova nella sottocartella BIN di MASM32. Nel caso del TASM il compilatore di risorse esiste in due versioni chiamate BRCC.EXE e BRCC32.EXE; il compilatore BRCC.EXE funziona sia sotto Windows che nella modalita' reale del DOS, mentre il compilatore BRCC32.EXE funziona invece solo nella modalita' protetta di Win32. Sia BRCC.EXE che BRCC32.EXE si trovano insieme a tutti gli altri strumenti di sviluppo, nella sottocartella BIN di TASM.
Per compilare il file RISORSE.RC con il MASM bisogna impartire il comando:
rc /v risorse.rc
Il parametro /v (verbose) fa in modo che RC.EXE visualizzi tutti i messaggi relativi alla fase di compilazione di RISORSE.RC; in questo modo possiamo avere maggiori informazioni nel caso in cui vengano trovati degli errori.
Per compilare il file RISORSE.RC con il TASM bisogna impartire il comando:
brcc32 risorse.rc
Si tenga presente che al posto dei compilatori di risorse forniti da MASM e TASM si possono utilizzare anche quelli forniti in dotazione ai vari ambienti di sviluppo per Windows.
Se la compilazione va a buon fine si ottiene un file chiamato RISORSE.RES; il formato interno di questo file e' compatibile con il formato OBJ (object). Come si puo' facilmente intuire, il file RISORSE.RES viene passato poi al linker insieme agli altri files in formato object; il linker provvedera' a collegare i vari object files producendo quindi l'eseguibile finale.

7.2 L'include file RISORSE.INC.

Per poter utilizzare le varie risorse dichiarate in RISORSE.RC, il modulo principale RISORSE.ASM deve conoscere tutti i nomi e le variabili simboliche che abbiamo dichiarato nel Resource File; in altre parole, il modulo principale RISORSE.ASM deve poter essere in grado di interfacciarsi con con il modulo RISORSE.RC. A tale proposito possiamo notare che nel blocco dati inizializzati (_DATA) del modulo RISORSE.ASM sono presenti le seguenti definizioni:
menuName       db       'RisorseMenu', 0
iconName       db       'RisorseIcon', 0

bitmap1Name    db       'RisorseBmp1', 0
bitmap2Name    db       'RisorseBmp2', 0
bitmap3Name    db       'RisorseBmp3', 0
bitmap4Name    db       'RisorseBmp4', 0

cursor1Name    db       'RisorseCursor1', 0
cursor2Name    db       'RisorseCursor2', 0
cursor3File    db       'libro.cur', 0
cursor4File    db       'penna.cur', 0
animcurFile    db       'dinosau2.ani', 0

AboutDlgName   db       'RisorseAboutDlg', 0
In questo modo vengono definite una serie di stringhe che contengono i nomi con i quali vengono identificate le varie risorse dichiarate in RISORSE.RC; e' importante notare che queste stringhe devono essere in formato C, dotate cioe' di zero finale.
In relazione ai cursori possiamo notare che solo due di essi vengono dichiarati nel file RISORSE.RC; gli altri 3 vengono invece caricati direttamente dal disco in fase di esecuzione del programma. Per gestire questi 3 cursori vengono definite nel blocco _DATA le 3 variabili cursor3File, cursor4File e animcurFile che devono puntare a stringhe C contenenti il nome del file CUR o ANI; in sostanza il programmatore ha la possibilita' di scegliere se incorporare direttamente nel programma una risorsa di tipo cursore, oppure se lasciarla in un file separato e caricarla poi in fase di esecuzione.
Il modulo RISORSE.ASM deve conoscere anche i codici numerici associati ai vari menu items; per evitare di dover lavorare con valori numerici espliciti, possiamo ridichiarare in RISORSE.ASM tutte le #define presenti in RISORSE.RC. Queste dichiarazioni possono essere inserite nel blocco tipi e costanti simboliche del modulo RISORSE.ASM; in alternativa, per rendere piu' semplici e snelli i vari moduli, possiamo servirci di un apposito include file che nel nostro esempio viene chiamato RISORSE.INC. Analizzando il file RISORSE.INC possiamo notare che al suo interno sono presenti le stesse #define di RISORSE.RC, ridichiarate naturalmente in stile Assembly; nello stesso file RISORSE.INC vengono inseriti anche i vari prototipi delle procedure utilizzate da RISORSE.ASM. Per evitare errori, si consiglia di utilizzare un editor dotato dello strumento copia/incolla; in questo modo si possono copiare le varie #define dal file RISORSE.RC per incollarle poi nel file RISORSE.INC.
A questo punto dobbiamo includere tutte queste dichiarazioni nel modulo RISORSE.ASM; a tale proposito, nella sezione inclusione librerie del modulo RISORSE.ASM possiamo notare la presenza della direttiva:
INCLUDE risorse.inc

7.3 Struttura generale dell'applicazione RISORSE.

L'applicazione RISORSE e' dotata di una semplice main window con barra dei menu e icona personalizzata; appena si avvia l'esecuzione, viene mostrata la main window con colore di sfondo personalizzato e con il classico cursore predefinito a forma di freccia. Attraverso il menu l'utente puo' selezionare svariate risorse di tipo bitmap, cursore, font e dialog; sempre attraverso il menu e' possibile richiedere la chiusura dell'applicazione.
Le bitmap selezionabili attraverso il popup "Bitmap" sono tutte da 128x128 pixel e vengono utilizzate per modificare lo sfondo della client area della main window; non si tratta quindi del riempimento della client area con una bitmap, ma di una vera e propria modifica del campo hbrBackground della struttura WNDCLASSEX che definisce lo sfondo della finestra.
Attraverso il popup "Cursori" e' possibile assegnare al puntatore del mouse 5 forme differenti una delle quali e' animata; dallo stesso popup si puo' tornare al cursore predefinito a forma di freccia. I primi due cursori personalizzati sono stati incorporati nel programma attraverso il file RISORSE.RC; gli altri tre cursori personalizzati vengono invece caricati dal disco in fase di esecuzione.
Attraverso il popup "Font" si possono selezionare 9 font differenti che vengono utilizzati per visualizzare una stringa; questa stringa viene posizionata al centro della client area mediante la tecnica mostrata nel precedente capitolo.
Attraverso il popup "Informazioni" e' possibile attivare una dialog box che mostra una serie di informazioni relative all'applicazione e all'autore del programma; come e' stato detto in precedenza, tutti i dettagli relativi alla gestione delle dialog box verranno illustrati alla fine del capitolo.

Per svolgere il suo lavoro l'applicazione RISORSE intercetta i 5 messaggi WM_CREATE, WM_PAINT, WM_COMMAND, WM_CLOSE e WM_DESTROY; analizziamo quindi il lavoro che viene svolto in fase di creazione della main window e in fase di elaborazione dei messaggi.

7.4 Installazione del menu e dell'icona.

L'installazione del menu e dell'icona personalizzata di un programma, avviene in modo molto semplice; tutto questo lavoro infatti viene svolto durante la fase di inizializzazione della main window. Nei precedenti capitoli abbiamo visto che questa fase consiste nel riempimento di una struttura di tipo WNDCLASSEX; a tale proposito, supponiamo di aver definito una variabile wc di tipo WNDCLASSEX. Tra i vari membri di questa struttura possiamo notare che uno di essi viene chiamato:
wc.lpszMenuName
Questo membro e' una DWORD che deve contenere l'identificatore della risorsa menu associata alla window class che stiamo inizializzando; negli esempi dei precedenti capitoli non abbiamo utilizzato nessuna risorsa menu, per cui questo membro veniva sempre inizializzato con:
mov wc.lpszMenuName, NULL
Nell'esempio di questo capitolo abbiamo invece definito un menu identificato simbolicamente dalla stringa RisorseMenu; per gestire questa stringa, nel blocco _DATA di RISORSE.ASM e' presente la definizione:
menuName db 'RisorseMenu', 0
A questo punto possiamo installare il menu della window class Risorse attraverso l'istruzione:
mov wc.lpszMenuName, offset menuName
In precedenza e' stato detto che una risorsa puo' essere identificata non solo attraverso una stringa, ma anche attraverso un codice numerico; anche in questo caso l'installazione della risorsa avviene in modo semplicissimo. Supponiamo ad esempio di voler gestire il menu del programma attraverso un codice numerico; a tale proposito nel file RISORSE.RC inseriamo ad esempio la dichiarazione:
#define ID_MENU 1001
Sempre nel file RISORSE.RC, l'intestazione del menu diventa:
ID_MENU MENU
Per poterci interfacciare a questa risorsa, nell'include file RISORSE.INC inseriamo la dichiarazione:
ID_MENU = 1001
A questo punto nella fase di inizializzazione della window class Risorse possiamo scrivere la semplicissima istruzione:
mov wc.lpszMenuName, ID_MENU
Come si puo' notare, questo secondo metodo e' anche piu' semplice di quello precedente, e presenta il vantaggio di farci risparmiare qualche byte; e' chiaro infatti che la costante ID_MENU richiede una quantita' di memoria inferiore a quella richiesta dalla stringa:
menuName db 'RisorseMenu', 0

Passiamo ora all'installazione dell'icona personalizzata che nel file RISORSE.RC e' stata dichiarata come:
RisorseIcon ICON "risorse.ico"
Nella struttura WNDCLASSEX e' presente un membro chiamato:
wc.hIcon
Questo membro e' una DWORD che deve contenere l'handle dell'icona da assegnare alla finestra che stiamo inizializzando; se vogliamo lasciare questa scelta a Windows possiamo scrivere:
mov wc.hIcon, NULL
In questo caso Windows utilizza un'icona predefinita; generalmente si tratta di una icona a forma di finestra. Se vogliamo utilizzare una specifica icona predefinita di Windows possiamo scrivere ad esempio:
call     LoadIcon, NULL, IDI_WARNING
mov      wc.hIcon, eax
In questo caso alla finestra che stiamo inizializzando viene associata un'icona predefinita contenente un segnale di pericolo con il punto esclamativo; e' importante notare che nel caso di installazione di icone predefinite, il primo parametro (hInstance) di LoadIcon deve essere NULL.
Se invece vogliamo utilizzare l'icona personalizzata dichiarata in RISORSE.RC, dobbiamo procedere esattamente come per il menu; prima di tutto nel blocco _DATA di RISORSE.ASM inseriamo la definizione:
iconName db 'RisorseIcon', 0
A questo punto possiamo installare questa icona attraverso le istruzioni:
call     LoadIcon, hInstance, offset iconName
mov      wc.hIcon, eax
L'argomento hInstance passato a LoadIcon e' naturalmente l'handle del programma che viene reperito come sappiamo tramite GetModuleHandle.
Se vogliamo gestire l'icona personalizzata attraverso un codice numerico, dobbiamo seguire lo stesso procedimento indicato per il menu; prima di tutto, nel file RISORSE.RC inseriamo ad esempio la dichiarazione:
#define ID_PROGICON 2001
Sempre nel file RISORSE.RC, l'intestazione della risorsa icona diventa quindi:
ID_PROGICON ICON "risorse.ico"
Per poterci interfacciare a questa risorsa, nell'include file RISORSE.INC inseriamo la dichiarazione:
ID_PROGICON = 2001
A questo punto nella fase di inizializzazione della window class Risorse possiamo scrivere le semplicissime istruzioni:
call     LoadIcon, hInstance, ID_PROGICON
mov      wc.hIcon, eax
La fase di installazione dell'icona di una applicazione, coinvolge anche l'icona piccola da 16x16 pixel che verra' visualizzata nella barra del titolo dell'applicazione stessa; a tale proposito notiamo che tra i vari membri di WNDCLASSEX ne troviamo uno che viene chiamato:
wc.hIconSm (handle to small icon)
Per installare l'icona piccola, dobbiamo seguire lo stesso identico procedimento descritto per l'icona da 32x32 pixel; come e' stato detto in un precedente capitolo, e' possibile delegare tutto questo lavoro a Windows scrivendo l'istruzione:
mov wc.hIconSm, NULL
In questo caso Windows utilizza per l'icona piccola una versione scalata a 16x16 pixel dell'icona da 32x32 pixel; tutto questo discorso naturalmente vale anche quando utilizziamo una icona personalizzata da 32x32 pixel.

Nella fase di inizializzazione della window class e' anche possibile installare un cursore personalizzato per il mouse; il procedimento da seguire e' identico a quello illustrato per le icone. Volendo ad esempio installare il cursore identificato dalla stringa:
cursor1Name db 'RisorseCursor1', 0
possiamo sfruttare l'apposito membro:
wc.hCursor
In questo caso dobbiamo scrivere:
call     LoadCursor, hInstance, offset cursor1Name
mov      wc.hCursor, eax
Nell'esempio RISORSE.ASM viene invece installato inizialmente il solito cursore predefinito a forma di freccia; abbiamo quindi le classiche istruzioni:
call     LoadCursor, NULL, IDC_ARROW
mov      wc.hCursor, eax
Tutte le risorse di tipo cursore e bitmap che abbiamo dichiarato nel file RISORSE.RC, vengono selezionate in fase di esecuzione attraverso il menu della main window; come e' stato detto in precedenza, la gestione del menu avviene tramite il messaggio WM_COMMAND.

7.5 Gestione del messaggio WM_CREATE.

La window procedure della window class Risorse intercetta il messaggio WM_CREATE per effettuare svariate inizializzazioni; a tale proposito la window procedure chiama la procedura Risorse_OnCreate. Le caratteristiche dei parametri ricevuti da questa procedura sono gia' state descritte nei precedenti capitoli; e' importante ribadire che la procedura che elabora il messaggio WM_CREATE, puo' essere considerata come il costruttore della window class che ha ricevuto il messaggio stesso.
All'interno della procedura Risorse_OnCreate vengono caricate le risorse bitmap e le risorse cursore utilizzate dal programma; viene inoltre riempita una struttura di tipo LOGFONT necessaria per poter utilizzare i font di Windows.

Per caricare una risorsa bitmap e ottenere il suo handle, possiamo utilizzare la procedura LoadBitmap; questa procedura viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
HBITMAP LoadBitmap(HINSTANCE hInstance, LPCTSTR lpBitmapName);
Il parametro hInstance come sappiamo e' l'handle dell'applicazione che viene reperito con GetModuleHandle.
Il parametro lpBitmapName e' l'indirizzo del nome simbolico (o il codice numerico) che identifica la risorsa bitmap.
La procedura LoadBitmap termina restituendo in EAX l'handle della bitmap (tipo di dato HBITMAP) appena caricata.
Nel caso ad esempio della bitmap identificata nel blocco _DATA con:
bitmap1Name db 'RisorseBmp1', 0
possiamo scrivere:
call     LoadBitmap, hInstance, offset bitmap1Name
mov      handleBitmap1, eax
Se invece nel file RISORSE.RC utilizziamo ad esempio la costante numerica ID_BITMAP1 per identificare questa bitmap, dobbiamo scrivere:
call     LoadBitmap, hInstance, ID_BITMAP1
mov      handleBitmap1, eax
Per caricare le risorse di tipo cursore utilizziamo la procedura LoadCursor che gia' conosciamo; come gia' sappiamo, se vogliamo caricare un cursore predefinito possiamo scrivere ad esempio:
call     LoadCursor, NULL, IDC_ARROW
mov      handleCursore, eax
Se invece vogliamo caricare un cursore personalizzato dichiarato in un Resource File, dobbiamo passare come primo argomento hInstance, e come secondo argomento l'indirizzo del nome simbolico che identifica il cursore stesso; nel caso ad esempio del cursore identificato nel blocco _DATA con:
cursor1Name db 'RisorseCursor1', 0
possiamo scrivere:
call     LoadCursor, hInstance, offset cursor1Name
mov      handleCursor1, eax
Come al solito, se nel file RISORSE.RC utilizziamo ad esempio la costante numerica ID_CURSOR1 per identificare questo cursore, dobbiamo scrivere:
call     LoadCursor, hInstance, ID_CURSOR1
mov      handleCursor1, eax
Se vogliamo caricare un cursore direttamente dal disco dobbiamo utilizzare la procedura LoadCursorFromFile; questa procedura viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
HCURSOR LoadCursorFromFile(LPCTSTR lpFileName);
Il parametro lpFileName e' l'indirizzo di una stringa C che contiene il nome del file ICO o ANI.
La procedura LoadCursorFromFile termina restituendo in EAX l'handle del cursore appena caricato.

All'interno di Risorse_OnCreate viene anche inizializzata la struttura di tipo LOGFONT che definisce le caratteristiche del font che vogliamo utilizzare; questo aspetto e' stato gia' illustrato nel precedente capitolo.

7.6 Gestione del messaggio WM_COMMAND.

Ogni volta che selezioniamo un menu item dal menu di una finestra, Windows invia automaticamente un messaggio WM_COMMAND alla window procedure della finestra stessa; come al solito, ulteriori informazioni associate ad un messaggio, vengono inviate alla window procedure attraverso i due parametri wParam e lParam.
Nel caso di WM_COMMAND la window procedure riceve tre informazioni che vengono chiamate id, hwndCtl e codeNotify; questi tre nomi sono stati "pescati" come al solito dall'header file windowsx.h. Alternativamente le informazioni relative agli argomenti associati ad un determinato messaggio possono essere ricavate anche dal solito Win32 Programmer's Reference; nel nostro caso dobbiamo aprire il file Win32.hlp, premere il pulsante Indice e richiedere la ricerca della parola chiave WM_COMMAND.

L'argomento id contiene un codice numerico che identifica il mittente del messaggio WM_COMMAND; nel caso di un menu l'argomento id contiene il codice del menu item che e' stato selezionato dall'utente e che ha provocato quindi l'invio del messaggio WM_COMMAND. L'argomento id viene inviato alla window procedure attraverso la WORD meno significativa del parametro wParam; programmando in linguaggio C si puo' sfruttare una macro di Windows chiamata LOWORD (Low Word = Word Bassa), con la quale si puo' scrivere ad esempio:
id = LOWORD(wParam);

L'argomento codeNotify contiene un codice numerico che ci permette di sapere se il messaggio WM_COMMAND proviene da un menu o da un cosiddetto control; con il termine control si indicano vari dispositivi di controllo di Windows come i pulsanti, le finestre di edit, etc. Se il messaggio WM_COMMAND arriva da un control l'argomento codeNotify vale 1; se invece il messaggio WM_COMMAND arriva da un menu l'argomento codeNotify vale 0.
L'argomento codeNotify viene inviato alla window procedure attraverso la WORD piu' significativa del parametro wParam; programmando in linguaggio C si puo' sfruttare una macro di Windows chiamata HIWORD (High Word = Word Alta), con la quale si puo' scrivere ad esempio:
codeNotify = HIWORD(wParam);

L'argomento hwndCtl contiene un codice numerico che rappresenta l'handle del control che ha inviato il messaggio WM_COMMAND; questo argomento quindi e' significativo solo per i control. Se il messaggio WM_COMMAND e' stato inviato da un menu, questo argomento vale NULL; l'argomento hwndCtl e' una DWORD che viene inviata alla window procedure attraverso il parametro lParam.

Seguendo le considerazioni esposte nei precedenti capitoli, possiamo utilizzare una apposita procedura per la gestione del messaggio WM_COMMAND; il prototipo C per questa procedura (ricavato dal file windowsx.h) e' il seguente:
void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
Nel file RISORSE.ASM questa procedura viene chiamata Risorse_OnCommand; in base alle considerazioni appena esposte, all'interno della procedura WndProc possiamo scrivere:
messaggio_WM_COMMAND:
   cmp      uMsg, WM_COMMAND           ; (uMsg == WM_COMMAND) ?
   jne      messaggio_WM_CLOSE
   mov      eax, wParam                ; eax = wParam
   mov      ecx, eax                   ; ecx = wParam
   and      eax, 0000ffffh             ; eax = id = LOWORD(wParam)
   shr      ecx, 16                    ; ecx = codeNotify = HIWORD(wParam)
   call     Risorse_OnCommand, hWnd, eax, lParam, ecx
   jmp      exitWndProc
Come si puo' notare, per estrarre da wParam i due argomenti id e codeNotify utilizziamo i due registri EAX e ECX; finche' e' possibile e' meglio evitare l'uso di registri come EBX, ESI e EDI il cui contenuto deve essere preservato dalle procedure di callback come WndProc.
Appena entrati in Risorse_OnCommand, possiamo iniziare ad elaborare i codici che arrivano dalla selezione dei vari menu items; a rigore sarebbe opportuno verificare prima di tutto il valore contenuto in codeNotify. Come e' stato detto in precedenza, se il messaggio WM_COMMAND proviene da un control, allora l'argomento codeNotify vale 1, mentre se il messaggio WM_COMMAND arriva da un menu, allora l'argomento codeNotify vale 0; nel nostro caso, non essendoci nessun control possiamo dare per scontato che il messaggio WM_COMMAND sia stato inviato in seguito alla selezione di un menu item.
Il menu associato all'applicazione RISORSE contiene ben 26 menu items, per cui all'interno di Risorse_OnControl dobbiamo elaborare le 26 possibili scelte effettuate dall'utente; attraverso il parametro id possiamo sapere quale menu item e' stato selezionato.
In precedenza e' stato detto che e' molto vantaggioso assegnare ad ogni popup un apposito gruppo di codici numerici tra loro consecutivi; in questo modo si possono ottenere notevoli semplificazioni nella fase di elaborazione dei vari codici, e si puo' rendere anche piu' compatta la procedura Risorse_OnCommand. Per rendercene conto possiamo osservare il seguente codice C che sfrutta appunto il fatto che i codici di ogni popup sono numeri interi consecutivi (il simbolo && in C rappresenta l'AND logico):
if ((id >= CM_MENU1a) && (id <= CM_MENUEXIT))
{
   /* elaborazione popup "Principale" */
}
else if ((id >= CM_BITMAP1) && (id <= CM_BITMAP5))
{
   /* elaborazione popup "Bitmap" */
}
else if ((id >= CM_CURSOR1) && (id <= CM_CURSOR6))
{
   /* elaborazione popup "Cursori" */
}
else if ((id >= CM_FONT1) && (id <= CM_FONT9))
{
   /* elaborazione popup "Font" */
}
else if ((id >= CM_HELP) && (id <= CM_ABOUT))
{
   /* elaborazione popup "Informazioni" */
}
In generale bisogna dire che in alcuni casi puo' essere conveniente elaborare in un colpo solo un intero popup, mentre in altri casi puo' essere necessario elaborare singolarmente i vari menu items presenti all'interno di un popup; nel seguito vengono illustrate queste due situazioni.

Partiamo quindi con l'analisi delle elaborazioni relative al popup "Principale"; in questo caso il parametro id puo' assumere uno tra i 4 codici che abbiamo chiamato simbolicamente CM_MENU1a, CM_MENU1b, CM_MENU1c e CM_MENUEXIT.
L'elaborazione dei primi 3 menu items consiste nel mostrare una MessageBox che visualizza il codice associato al parametro id; in questo modo possiamo constatare che effettivamente il parametro id contiene proprio il codice che nel file RISORSE.RC abbiamo associato al menu item appena selezionato. In questo caso e' conveniente elaborare questi primi 3 menu items in un unico blocco di istruzioni; cosi' facendo possiamo risparmiare parecchio codice inutile e dispersivo.
Per convertire il contenuto di id in una stringa, utilizziamo come al solito la procedura wsprintf; la stringa cosi' ottenuta viene poi inviata alla MessageBox. E' importante ribadire ancora una volta che in una situazione di questo genere, la MessageBox e' "figlia" della main window che la sta chiamando; di conseguenza il primo argomento passato alla MessageBox deve essere l'handle (hwnd) della main window.
L'arrivo del codice id = CM_MENUEXIT indica che l'utente attraverso il menu ha richiesto la chiusura dell'applicazione RISORSE.EXE; in una situazione del genere dovremmo in teoria chiamare la procedura Risorse_OnClose che elabora il messaggio WM_CLOSE. Questa soluzione pero' e' da evitare in quanto le procedure che elaborano i vari messaggi inviati ad una finestra, dovrebbero essere chiamate solo dalla relativa window procedure; la soluzione ideale consiste allora nel forzare Windows a generare un messaggio WM_CLOSE che verra' poi intercettato da WndProc. A tale proposito ci possiamo servire dell'apposita procedura SendMessage che ci permette di inviare un messaggio ad una window procedure; questa procedura viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
hWnd e' l'handle della finestra che ha chiamato SendMessage; in questo modo SendMessage sa a quale window procedure deve inviare il messaggio.
Msg e' il codice numerico del messaggio da inviare; nel nostro caso si tratta ovviamente del codice WM_CLOSE.
wParam e lParam contengono informazioni aggiuntive da inviare alla window procedure; nel nostro caso non dobbiamo inviare nessuna informazione aggiuntiva, per cui questi due parametri valgono entrambi NULL.
SendMessage termina restiruendo in EAX un codice che dipende dal messaggio che abbiamo spedito.

L'istruzione:
call SendMessage, hwnd, WM_CLOSE, NULL, NULL
ha come effetto la chiamata diretta di WndProc; la window procedure riceve quindi il messaggio WM_CLOSE e chiama Risorse_OnClose che elabora poi questo messaggio nel modo gia' illustrato nei precedenti capitoli.
La procedura SendMessage lavora in modo sincrono con il nostro programma; questo significa che SendMessage restituisce il controllo solo quando l'elaborazione di WM_CLOSE e' terminata. In alternativa Windows mette a disposizione anche la procedura PostMessage; questa procedura ha gli stessi identici parametri di SendMessage, ma presenta la caratteristica di lavorare in modo asincrono. In pratica PostMessage invece di inviare direttamente Msg ad una window procedure, si limita ad inserire Msg nella coda dei messaggi della stessa window procedure; dopo aver svolto questo lavoro PostMessage restituisce subito il controllo al caller. In generale il programmatore in base al contesto, deve essere in grado di stabilire se sia meglio chiamare SendMessage o PostMessage; nel nostro caso e' stata usata SendMessage in quanto l'utente ha richiesto la chiusura dell'applicazione, e quindi prima di proseguire e' necessario attendere una eventuale conferma di questa decisione.

Passiamo ora all'elaborazione del popup "Bitmap"; in questo caso il parametro id puo' assumere uno tra i 5 codici che abbiamo chiamato simbolicamente CM_BITMAP1, CM_BITMAP2, CM_BITMAP3, CM_BITMAP4 e CM_BITMAP5. Attraverso i primi 4 menu items l'utente sta chiedendo di modificare lo sfondo della main window con una apposita bitmap; attraverso il quinto menu item l'utente sta chiedendo di tornare allo sfondo originale della finestra.
Gli handle delle bitmap selezionabili sono gia' stati caricati dalla procedura Risorse_OnCreate; vediamo allora come si deve procedere per utilizzare queste bitmap come sfondo della main window.
Nei precedenti capitoli abbiamo gia' conosciuto le due procedure CreateSolidBrush (crea un pennello solido) e CreateHatchBrush (crea un pennello retinato); questa volta utilizziamo invece una nuova procedura chiamata CreatePatternBrush. Questa procedura viene definita nella libreria GDI32.LIB e presenta il seguente prototipo:
HBRUSH CreatePatternBrush(HBITMAP hBmp);
hBmp e' l'handle di una bitmap che verra' utilizzata come tratto del pennello.
La procedura CreatePatternBrush termina restituendo in EAX l'handle del pennello selezionato; in caso di errore si ottiene EAX=NULL.
In pratica, il pennello fornitoci da CreatePatternBrush ci permette di disegnare con un tratto che assume la forma (pattern) della bitmap specificata come argomento; nel caso di Windows 95 la bitmap deve essere da 8x8 pixel (se si utilizza una bitmap piu' grande, Windows 95 taglia una porzione da 8x8 pixel della bitmap stessa).
Se vogliamo riempire lo sfondo della main window con la bitmap puntata da handleBitmap1, possiamo scrivere:
call CreatePatternBrush, handleBitmap1
In questo modo otteniamo in EAX un pennello che dobbiamo passare alla procedura SetClassLong; in fase di esecuzione di un programma, questa procedura ci permette di modificare i vari membri della struttura WNDCLASSEX di una finestra (compresa la window procedure). La procedura SetClassLong viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
DWORD SetClassLong(HWND hWnd, int nIndex, LONG dwNewLong);
hWnd e' l'handle della window class da modificare.
nIndex e' un codice numerico che ci permette di specificare il membro di WNDCLASSEX che vogliamo modificare.
dwNewLong e' una DWORD che contiene il nuovo valore da assegnare al membro di WNDCLASSEX che vogliamo modificare.
La procedura SetClassLong termina restituendo in EAX il vecchio valore contenuto nel membro di WNDCLASSEX che abbiamo modificato; in caso di errore viene restituito EAX=0.
Il problema che si presenta e' dato dal fatto che nel caso di modifica dello sfondo di una finestra, la procedura SetClassLong non produce nessun effetto immediato; per veder comparire il nuovo sfondo dobbiamo costringere Windows ad aggiornare sia lo sfondo sia il contenuto della finestra. Un modo per ottenere facilmente questo risultato consiste nel chiamare la procedura InvalidateRect; questa procedura viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
BOOL InvalidateRect(HWND hWnd, CONST RECT *lpRect, BOOL bErase);
hWnd e' l'handle della finestra da aggiornare.
lpRect e' l'indirizzo di una struttura RECT che contiene la porzione rettangolare della finestra da aggiornare; se vogliamo aggiornare l'intera finestra dobbiamo passare lpRect=NULL.
bErase e' un flag che ci permette di richiedere o meno la cancellazione del vecchio sfondo; se bErase=TRUE viene cancellato il vecchio sfondo.
Se la procedura InvalidateRect termina con successo restituisce un valore non nullo in EAX; in caso contrario si ottiene EAX=0.
La chiamata:
call InvalidateRect, hwnd, NULL, TRUE
forza Windows ad inviare a WndProc i messaggi WM_ERASEBKGND e WM_PAINT; il messaggio WM_ERASEBKGND comporta la richiesta di aggiornamento dello sfondo di una finestra. Naturalmente il messaggio WM_ERASEBKGND non viene intercettato da WndProc, per cui va a finire a DefWindowProc e viene quindi gestito direttamente da Windows; il messaggio WM_PAINT viene invece smistato da WndProc alla procedura Risorse_OnPaint che provvede ad aggiornare l'output della nostra finestra.
In base alle considerazioni appena esposte, se vogliamo riempire lo sfondo con la bitmap puntata da handleBitmap1 possiamo scrivere:
call     CreatePatternBrush, handleBitmap1
call     SetClassLong, hwnd, GCL_HBRBACKGROUND, eax
call     InvalidateRect, hwnd, NULL, TRUE
La costante simbolica GCL_HBRBACKGROUND ci permette di richiedere a SetClassLong la modifica dello sfondo di una finestra; per conoscere le altre costanti simboliche si puo' consultare il Win32 Programmer's Reference.
Come si puo' notare, e' fondamentale passare a InvalidateRect l'argomento bErase=TRUE; in caso contrario lo sfondo della finestra non viene aggiornato.
Quando l'utente seleziona il menu item associato al codice id=CM_BITMAP5, ci sta chiedendo di ripristinare lo sfondo originario; in questo caso ci basta passare a SetClassLong un pennello ottenuto con CreateSolidBrush e dotato dello stesso colore che avevamo scelto in fase di riempimento della struttura WNDCLASSEX.
I concetti appena esposti, possono essere applicati anche per richiedere uno sfondo di tipo bitmap in fase di riempimento della struttura WNDCLASSEX; possiamo scrivere ad esempio:
call     LoadBitmap, hInstance, offset bitmap1Name
call     CreatePatternBrush, eax
mov      wc.hbrBackground, eax

Attraverso il popup "Cursori" l'utente puo' selezionare 6 forme differenti per il cursore del mouse; i codici associati a questi 6 menu items vanno da CM_CURSOR1 a CM_CURSOR6. I codici da CM_CURSOR1 a CM_CURSOR5 permettono di selezionare dei cursori personalizzati i cui handle sono stati gia' caricati da Risorse_OnCreate; il codice CM_CURSOR6 permette di tornare al cursore predefinito a forma di freccia.
Anche per i cursori dobbiamo utilizzare la procedura SetClassLong; il secondo argomento da passare a questa procedura e' la costante simbolica GCL_HCURSOR, mentre il terzo argomento e' l'handle del nuovo cursore. Per sostituire ad esempio il cursore corrente con il nuovo cursore puntato da handleCursor2 possiamo scrivere:
call SetClassLong, hwnd, GCL_HCURSOR, handleCursor2
Questa chiamata provoca l'immediata visualizzazione del nuovo cursore; il cursore animato associato al codice CM_CURSOR5 e' stato "preso in prestito" dalla cartella windows\cursors di Windows XP.
Il codice CM_CURSOR6 permette di tornare al cursore predefinito a forma di freccia; in questo caso bisogna passare a SetClassLong l'handle del cursore IDC_ARROW che ci viene restituito come gia' sappiamo dala chiamata:
call LoadCursor, NULL, IDC_ARROW.

Attraverso il popup "Font" e' possibile selezionare 9 font differenti che verranno utilizzati per visualizzare una stringa al centro della client area; i codici associati a questi 9 menu items vanno da CM_FONT1 a CM_FONT9.
Appena viene selezionato un nuovo font viene chiamata la procedura SetFont che provvede a caricare nel membro lfFaceName della struttura LOGFONT il nome del font selezionato; subito dopo SetFont carica nella variabile globale handleFont l'handle del nuovo font selezionato. Il procedimento e' lo stesso gia' mostrato nel precedente capitolo; nel nostro esempio e' fondamentale che handleFont venga definito nel blocco _DATA per poter essere inizializzato con 0. A questo punto bisogna forzare Windows a generare un messaggio WM_PAINT in modo che la procedura Risorse_OnPaint ridisegni la stringa con il nuovo font appena selezionato; e' necessario richiedere anche il ridisegno dello sfondo per evitare che la nuova stringa venga disegnata su quella vecchia producendo cosi' un pasticcio illegibile. Per ottenere questo risultato possiamo sfruttare di nuovo la procedura InvalidateRect che deve ricevere TRUE come terzo argomento (bErase).
La chiamata di InvalidateRect produce automaticamente un messaggio WM_PAINT che viene intercettato da WndProc e inoltrato a Risorse_OnPaint; la procedura Risorse_OnPaint verifica se handleFont e' diverso da zero, e in caso affermativo disegna una stringa al centro della client area attraverso la tecnica che gia' conosciamo.

L'ultimo popup chiamato "Informazioni" contiene due menu items; il primo (CM_HELP) provoca la comparsa di una semplice MessageBox, mentre il secondo (CM_ABOUT) provoca la comparsa di una dialog box che viene descritta piu' avanti.

7.7 Gestione del messaggio WM_DESTROY.

Il messaggio WM_DESTROY viene gestito dalla procedura Risorse_OnDestroy; questa procedura puo' essere vista come il distruttore della window class Risorse. All'interno di Risorse_OnDestroy dobbiamo effettuare quindi tutte le necessarie deinizializzazioni; in particolare dobbiamo restituire a Windows tutte le risorse non piu' necessarie alla nostra applicazione. Analizzando il codice di RISORSE.ASM possiamo notare che prima di tutto vengono distrutti con DeleteObject gli handle di tutte le bitmap che abbiamo utilizzato nel programma; come gia' sappiamo, DeleteObject puo' essere utilizzata per distruggere diverse risorse tra le quali si possono citare le penne, i pennelli i font e le bitmap.
Gli handle dei cursori caricati con LoadCursor o LoadCursorFromFile, non devono essere distrutti; questo lavoro infatti viene svolto da Windows in fase di distruzione della window class.
Successivamente Risorse_OnDestroy distrugge con DeleteObject anche l'handle che abbiamo utilizzato per gestire i font; l'ultimo compito svolto da Risorse_OnDestroy consiste come al solito nel chiamare PostQuitMessage.

7.8 La dialog box RisorseAboutDlg.

Come e' stato detto in precedenza, le dialog box sono delle finestre che permettono ad un utente di dialogare con una applicazione; l'esempio RISORSE.ASM utilizza una semplice dialog box che puo' essere usata per mostrare alcune informazioni relative al programma e all'autore del programma stesso.
Gli ambienti di sviluppo piu' sofisticati come Microsoft Visual C++, Borland C/C++ Compiler, Borland Delphi, etc, mettono a disposizione appositi strumenti (tools) visuali che permettono di disegnare letteralmente le dialog box; in questo modo e' possibile creare in modo efficiente delle dialog box estremamente complesse, che verranno poi tradotte automaticamente nel tipico pseudo codice che si utilizza nei files di risorse.
Se non si dispone di questi tools e' ugualmente possibile progettare "a naso" le proprie dialog box; a tale proposito ci possiamo munire di carta e penna per tracciare uno schema della dialog box che vogliamo creare. Una volta che il disegno e' pronto, possiamo procedere alla traduzione "manuale" della dialog box in pseudo codice da inserire nel resource file della nostra applicazione.

Nel file RISORSE.RC vediamo un esempio pratico su come sia possibile creare facilmente delle semplici dialog box; il codice che dichiara una dialog box deve iniziare con una intestazione del tipo:
nome_simbolico DIALOG x, y, larghezza, altezza
Il nome_simbolico ci permette di identificare la dialog box che stiamo creando; anche in questo caso possiamo utilizzare una stringa oppure un codice numerico. Dopo la parola chiave DIALOG incontriamo 4 parametri che rappresentano le coordinate x, y del vertice in alto a sinistra della finestra, la larghezza e l'altezza della finestra; le due coordinate x, y sono espresse in pixel e vengono calcolate rispetto al vertice in alto a sinistra dello schermo. In relazione invece alla larghezza e all'altezza di una dialog box e' necessario fare una precisazione che assume una importanza enorme; a differenza di quanto accade per le normali finestre, la larghezza e l'altezza di una dialog box vengono espresse non in pixel ma in dialog units. Per le larghezze (asse orizzontale) l'unita' di misura usata e' pari a 1/4 della larghezza media in pixel dei caratteri del font utilizzato; per le altezze (asse verticale) l'unita' di misura usata e' pari a 1/8 dell'altezza media in pixel dei caratteri del font utilizzato. Se non si seleziona nessun font personalizzato, le unita' di misura vengono riferite al font di sistema di Windows (font predefinito); il font di sistema e' un font a spaziatura fissa simile a quello usato dal DOS. L'utilizzo delle dialog units si rende necessario perche' in questo modo le proporzioni delle dialog box vengono rese indipendenti (device independent) dalla risoluzione grafica dello schermo; se non si adottasse questo accorgimento, ogni volta che si modifica la risoluzione grafica dello schermo verrebbero completamente alterate le proporzioni delle dialog box.

Tornando al file RISORSE.RC possiamo notare che dopo l'intestazione della dialog box troviamo un'altra riga che inizia con la parola chiave STYLE; in questa riga possiamo dichiarare una serie di aspetti stilistici della dialog box. A tale proposito possiamo utilizzare una serie di costanti simboliche molte delle quali coincidono con quelle utilizzate per la main window nella procedura CreateWindowEx; tutte queste costanti devono essere combinate tra loro con l'operatore | del C (codice ASCII 124) che equivale all'operatore OR (bit-a-bit) dell'Assembly. La costante DS_CENTER richiede che la dialog box venga centrata sullo schermo, la costante WS_CAPTION abilita la barra del titolo, la costante WS_SYSMENU abilita il menu di sistema, la costante WS_VISIBLE fa in modo che la dialog box diventi visibile non appena viene attivata, etc; per maggiori dettagli su queste costanti si puo' consultare il Win32 Programmer's Reference.
Dopo la riga STYLE incontriamo un'altra riga che inizia con la parola chiave CAPTION; questa riga ci permette di specificare una stringa (tra doppi apici) che comparira' nella barra del titolo della dialog box.
La riga successiva inizia con la parola chiave FONT e ci permette di specificare l'altezza e il nome (tra doppi apici) del font da utilizzare all'interno della dialog box; se il font che abbiamo richiesto non e' disponibile, verra' utilizzato il solito font di sistema. In base a quanto e' stato detto in precedenza, l'altezza del font selezionato influenzera' le dimensioni e l'aspetto generale della dialog box; di conseguenza, si consiglia di sceglere dei font molto comuni, che siano disponibili su tutti i computers.
Se vogliamo aggiungere un menu alla nostra dialog box dobbiamo utilizzare la parola chiave MENU seguita da una stringa tra doppi apici che identifica il menu stesso; a titolo di curiosita', nella dichiarazione della nostra dialog box si puo' provare ad aggiungere la riga:
MENU "RisorseMenu"
A questo punto inizia il corpo della dialog box; tutte le informazioni presenti nel corpo devono essere racchiuse tra parentesi grafe { }, oppure tra le parole chiave BEGIN END. All'interno del corpo di una dialog box possono comparire svariati oggetti come bitmap, icone, pulsanti, stringhe di testo, controlli di input/output, etc; questi oggetti vengono trattati come se fossero finestre figlie (child windows) della stessa dialog box.

Come si puo' notare nel file RISORSE.RC, il corpo della nostra dialog box inizia con una serie di oggetti di tipo CTEXT; un controllo di tipo CTEXT ci permette di inserire una stringa di testo centrato nell'area rettangolare creata dal controllo stesso. La dichiarazione di un controllo CTEXT assume in generale il seguente aspetto:
CTEXT "stringa di testo", id, x, y, laghezza, altezza, stile
La stringa tra doppi apici rappresenta il testo che verra' visualizzato all'interno di un CTEXT.
id e' il codice numerico che utilizziamo per identificare il controllo CTEXT; questo codice viene inviato da Windows insieme al messaggio WM_COMMAND generato dal controllo CTEXT. Siccome i controlli CTEXT hanno il compito di mostrare una semplice stringa statica, il parametro id non ha nessun significato; in questo caso bisogna utilizzare il valore predefinito -1.
x, y, larghezza, altezza rappresentano ovviamente le caratteristiche geometriche della finestra associata al CTEXT, e vengono espresse in dialog units; anche x, y quindi sono espresse in dialog units e sono riferite al vertice in alto a sinistra della dialog box. Queste considerazioni sono valide per tutti gli oggetti presenti nel corpo di una dialog box.
L'ultimo parametro di CTEXT definisce gli aspetti stilistici di questo controllo; la costante WS_CHILD indica che la finestra del CTEXT e' confinata all'interno della dialog box, mentre la costante WS_BORDER permette di assegnare un bordo in 3d alla finestra stessa.

Dopo i vari oggetti CTEXT incontriamo un oggetto di tipo PUSHBUTTON che dichiara un controllo a pulsante; la dichiarazione di un controllo PUSHBUTTON assume in generale il seguente aspetto:
PUSHBUTTON "stringa di testo", id, x, y, laghezza, altezza, stile
Il significato di questi parametri e' identico al caso di CTEXT; questa volta pero' il parametro id diventa importante in quanto ci permette di sapere quando il pulsante e' stato premuto. A tale proposito si puo' notare che viene utilizzata la costante simbolica IDOK che e' la stessa restituita da una MessageBox quando si preme il suo pulsante Ok; in alternativa possiamo utilizzare anche un apposito codice numerico personalizzato.
Lo stile WS_TABSTOP indica tutti quei controlli che possiamo selezionare in sequenza attraverso la pressione del tasto [Tab].

L'ultimo oggetto presente nella nostra dialog box viene identificato dalla patola chiave CONTROL; la dichiarazione di un oggetto CONTROL assume in generale il seguente aspetto:
CONTROL nome_simbolico, id, "tipo controllo", stile, x, y, laghezza, altezza
nome_simbolico e' il nome che utilizziamo per identificare il controllo e puo' essere una stringa tra doppi apici o un valore numerico.
id identifica il codice associato al controllo; se questo codice non e' significativo bisogna usare come al solito -1.
tipo_controllo e' una parola chiave tra doppi apici che identifica il tipo di controllo; nel nostro caso, dovendo definire un controllo di tipo bitmap dobbiamo utilizzare la parola chiave STATIC.
Gli altri parametri hannno il solito significato; nel caso di CONTROL di tipo icona o bitmap, i parametri larghezza e altezza vengono ignorati.
Per definire un controllo di tipo bitmap bisogna inserire la costante di stile SS_BITMAP; per definire un controllo di tipo icona bisogna inserire la costante di stile SS_ICON.
Nel nostro caso viene definito un controllo di tipo bitmap che utilizza il nome simbolico "RisorseLogo"; questo nome e' stato dichiarato in precedenza nel file RISORSE.RC, e identifica la bitmap "ramlogo.bmp".

Tutte le informazioni sulla dialog box dichiarata in RISORSE.RC vengono utilizzate nel file RISORSE.ASM per gestire la dialog box stessa; vediamo allora in dettaglio come si sviluppa questa gestione.
Con il menu item associato al codice CM_ABOUT l'utente sta richiedendo la visualizzazione della nostra dialog box; per rispondere a questa richiesta possiamo utilizzare la procedura DialogBoxParam. Questa procedura viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
int DialogBoxParam(HINSTANCE hInstance,   /* istanza dell'applicazione */
                   LPCTSTR lpTemplateName,/* indirizzo nome simbolico dialog box */
                   HWND hwndParent,       /* handle della finestra madre */
                   DLGPROC lpDialogFunc,  /* indirizzo window procedure */
                   LPARAM dwInitParam     /* informazioni di inizializzazione */
);
hInstance e' l'handle dell'applicazione a cui appartiene la dialog box; nel nostro caso la dialog box appartiene all'applicazione RISORSE, per cui dobbiamo utilizzare il solito hInstance che avevamo reperito con GetModuleHandle.
lpTemplateName e' l'indirizzo del nome simbolico oppure il codice numerico che identifica la dialog box; nel nostro caso utilizziamo il nome simbolico RisorseAboutDlg.
hwndParent e' l'handle della finestra "madre" che ha creato la dialog box; nel nostro caso si tratta dell'handle della window class Risorse.
lpDialogFunc e' l'indirizzo della window procedure che elabora i messaggi inviati da Windows alla dialog box; come accade per tutte le finestre, anche le dialog box devono specificare quindi la loro window procedure.
dwInitParam e' una DWORD che ci permette di inviare ulteriori informazioni per la fase di creazione della dialog box; se non esiste nessuna informazione aggiuntiva da inviare, questo parametro deve valere NULL.
Se DialogBoxParam termina con successo restituisce in EAX il valore di ritorno della dialog box; in caso contrario si ottiene EAX=-1.

All'interno della procedura Risorse_OnCommand la risposta al menu item CM_ABOUT e' rappresentata dalla chiamata:
call DialogBoxParam, hInstance, offset AboutDlgName, hwnd, offset AboutWinProc, NULL
La window procedure della nostra dialog box e' rappresentata quindi dalla procedura AboutWinProc; questa procedura ha una struttura del tutto simile a quella di WndProc.
La procedura DialogBoxParam provvede anche a creare un loop dei messaggi per la dialog box appena attivata; questo loop dei messaggi viene gestito direttamente da Windows. La chiamata di DialogBoxParam determina l'invio da parte di Windows del messaggio WM_INITDIALOG; questo messaggio e' l'equivalente di WM_CREATE e quindi viene inviato prima che la dialog box venga visualizzata. In questo caso il parametro lParam di AboutWinProc riceve il contenuto del parametro dwInitParam inviato da DialogBoxParam; l'elaborazione del messaggio WM_INITDIALOG termina in genere con la restituzione del valore TRUE in EAX.
A questo punto la nostra dialog box viene visualizzata sullo schermo, ed e' pronta per ricevere altri messaggi; la gestione di questi messaggi si svolge in modo del tutto analogo a quanto abbiamo visto per WndProc, con la differenza che questa volta non dobbiamo chiamare nessuna window procedure predefinita (come DefWindowProc).
Osserviamo in particolare che AboutWinProc intercetta il messaggio WM_COMMAND che ci permette di interfacciarci con i vari controlli inseriti nella dialog box; quando viene premuto ad esempio il pulsante 'Ok' della dialog box, viene inviato il messaggio WM_COMMAND accompagnato dall'id del pulsante premuto. Questo id si trova come al solito nella WORD meno significativa del parametro wParam ricevuto da AboutWinProc; si tratta del codice IDOK che abbiamo assegnato al PUSHBUTTON nel file RISORSE.RC.
Quando l'utente preme il pulsante di chiusura della dialog box, o seleziona Chiudi dal menu di sistema, viene inviato un messaggio WM_COMMAND accompagnato da id = IDCANCEL; in risposta all'arrivo del codice IDOK o IDCANCEL dobbiamo chiudere la dialog box. A tale proposito ci dobbiamo servire della procedura EndDialog; questa procedura viene definita nella libreria USER32.LIB e presenta il seguente prototipo:
BOOL EndDialog(HWND hDlg, int nResult);
hDlg e' l'handle che AboutWinProc ha ricevuto come primo argomento.
nResult e' il valore di ritorno restituito dalla dialog box prima di terminare; nel nostro caso la dialog box termina con il valore TRUE che comunque viene ignorato.
Se EndDialog termina con successo restituisce un valore non nullo in EAX; in caso contrario si ottiene EAX=0.

Un'ultima considerazione rigurada il fatto che la procedura DialogBoxParam crea una dialog box di tipo modal (modale); una dialog box modale quando viene aperta toglie il controllo alla finestra "madre" fino a quando non viene chiusa. All'estremo opposto troviamo invece le dialog box di tipo modeless (non modali); questo tipo di dialog box non toglie il controllo alla finestra "madre", e questo significa che possiamo saltare dalla finestra "madre" alla finestra "figlia" o viceversa.

7.9 Generazione dell'eseguibile RISORSE.EXE.

Per generare l'eseguibile RISORSE.EXE con il MASM bisogna uscire al prompt del DOS, posizionarsi nella cartella:
c:\masm32\win32asm\Risorse
e digitare:
risorse.bat
Per generare l'eseguibile RISORSE.EXE con il TASM bisogna uscire al prompt del DOS, posizionarsi nella cartella:
c:\tasm\win32asm\Risorse
e digitare:
..\..\bin\make -B -frisorse.mak

Se analizziamo ora le dimensioni dei vari files, possiamo notare quanto segue (caso del TASM):
Il file RISORSE.OBJ generato dall'assembler occupa su disco circa 5 Kb.
Il file RISORSE.RES generato dal compilatore di risorse occupa su disco circa 79 Kb.
Il file RISORSE.EXE generato dal linker occupa su disco circa 88 Kb.
Tutto questo ci fa capire che le risorse incorporate in un programma influiscono in modo drastico sulle dimensioni dell'eseguibile finale; nel caso di RISORSE.EXE si vede che le risorse occupano quasi il 90% dell'eseguibile.
In ogni caso, gli appena 88 Kb del nostro programma scritto in Assembly, appaiono ridicoli al cospetto delle parecchie centinaia di Kb dello stesso programma scritto in Java o in Visual Basic; non parliamo poi delle prestazioni perche' in questo caso il confronto diventa veramente penoso.