Sincronizza Indice |
Scarica il progetto |
Testo dell'articolo |
Stampa l'articolo |
Avete mai pensato di inserire un pulsante sulla barra del titolo di un
form? Il problema in questione è risolvibile solamente con un'operazione di subclassing di un semplice CommandButton. Infatti è impossibile spostare un pulsante sulla barra del titolo tramite le normali istruzioni di Visual Basic e, anche se riuscissimo a farlo, non esiste un evento del form che rilevi il suo spostamento.
Pertanto, tramite una funzione di hooking, rintracceremo i messaggi diretti alla finestra del pulsante e in tal modo potremo eseguire operazioni particolari legate all'invio di messaggi non legati ad eventi dell'oggetto subclassato. Attenzione! In questo progetto avremo bisogno di due formse di un modulo standard per le dichiarazioni API. Cominciamo disegnando l'interfaccia grafica: il primo form si chiamerà frmMain e sarà quello che conterrà il pulsante che prenderà posto sulla barra del titolo. Il form conterrà una Labeldescrittiva senza alcuna funzionalità e due CommandButtondi cui il primo si chiamerà cmdTest ed avrà queste proprietà:
Il secondo pulsante si chiamerà Command1 e conterrà il testo "Altro form". Servirà a caricare il secondo form e dimostrare il comportamento del primo pulsante in presenza di altri forms. Disegniamo rapidamente anche l'altro form, di nome Form1. Esso conterrà soltanto un CommandButton di nome Command1, con la Caption impostata a "Chiudi". Questo secondo form non farà nulla; il suo utilizzo serve soltanto per disattivare il primo form. Passiamo al codice. Dentro il modulo standard scriviamo:
Fino a qui abbiamo le varie dichiarazioni API:
Il tipo Rect viene richiesto dalla funzione GetWindowRect per ottenere le coordinate assolute della finesta.
Il tipo CWPSTRUCT - CallWindowProcedureStruct - viene utilizzato dalla nostra Window Procedure per contenere i parametri passati dalla Window Procedure originale.
Seguono alcune costanti per definire i comportamenti da effettuare tramite le funzioni API viste poco prima. Le costanti alle righe 23, 24 e 25 sono i tre messaggi che intercetteremo tramite la Window Procedure e in base ad essi svogeremo determinate operazioni. La variabile oldproc (riga 31) contiene il puntatore alla locazione della Window Procedure originale. La variabile verrà utilizzata per ripristinare lo stato normale delle al termine del programma. La variabile cmdButtonHwnd contiene l'handle del pulsante e verrà utilizzata da alcune funzioni API per decidere la posizione che la finestra del pulsante deve assumere.
Alla riga 34 inizia la nostra Window Procedure, di nome CallWndProc. Essa dovrà processare tre messaggi: WM_COMMAND, WM_NCPAINT e WM_MOVE.
Così, alla riga 36, abbiamo la scelta del messaggio passato. Se esso è WM_COMMAND, ovvero la pressione del pulsante (riga 38), esso verrà prima nascosto (riga 39), sarà mostrata una finestra con un messaggio (riga 40) e poi sarà nuovamente reso visibile (riga 41). Se il messaggio passato alla Window Procedure è WM_NCPAINT (ridisegna la finestra) oppure WM_MOVE (finestra spostata), verranno estratte le coordinate assolute del form frmMain tramite la funzione GetWindowRect (riga 44) e poi verrà spostato il pulsante il cui handle è cmdButtonHwnd (ovvero il pulsante cmdTest) tramite la funzione SetWindowPos. Viene effettuato qualche calcolo per determinare la posizione che il pulsante dovrà assumere sulla barra del titolo (riga 45). Tutti gli altri messaggi inviati alla finestra del form saranno ignorati. Torniamo al form principale (frmMain) e scriviamo queste righe di codice:
L'hooking della finestra viene effettuato in occasione del caricamento del Form, all'interno dell'eventoLoad. Alla riga 4 viene salvato l'handle del pulsante cmdTest, per riutilizzarlo in seguito all'interno del modulo. Alla riga 5 viene installata la nuova Window Procedure. La funzione SetWindowsHookEx
richiede fra l'altro, un puntatore alla funzione Window Procedure. Esso
viene fornito tramite l'operatore AddressOf. La costante WH_CALLWNDPROC
indica che la nostra Window Procedure riceverà i messaggi prima
che il sistema li invii alla Window Procedure originale. La riga 6 cambia lo stile della finestra del pulsante, trasformandola in una finestra invisibile nella barra delle applicazioni, perché alla prossima riga la finestra del pulsante diventerà un'applicazione vera e propria, non sottomessa ai limiti imposti dal form. Alla riga 7 viene cambiata la finestra genitore del pulsante cmdTest, e viene impostata come finestra genitore la finestra contenente il form, ovvero il Program Manager stesso, il programma che gestisce le finestre di tutti gli altri programmi. Ecco perché abbiamo dovuto cambiare lo stile della finestra alla riga 6. Se non l'avessimo fatto avremmo visto l'icona del pulsante ? sulla barra delle applicazioni.
Alla chiusura del form, nell'eventoUnload, abbiamo lo sganciamento (unhooking) della Window Procedure, tramite la chiamata della funzione UnhookWindowsHookEx con il parametro della Window Procedure originale. Viene anche ripristinata la finestra genitore del pulsante, reimpostandogli come genitore il form frmMain.
Abbiamo voluto aggiungere questa piccola routine per dimostrare che la finestra del pulsantino cmdTest diviene un programma del tutto separato. Infatti al click sopra il pulsante non viene generato più l'evento Click, gestito dalla nostra applicazione.
L'esecuzione di questo evento non avverrà mai.
Alla riga 19 abbiamo gestito l'evento Click del pulsante Command1. Esso inizialmente nasconderà il pulsante sulla barra del titolo (riga 20), caricherà e mostrerà il secondo form, di nome Form1. Questa funzione serve soltanto a dimostrare il funzionamento del pulsante sulla barra del titolo in presenza di più forms nella stessa applicazione.
Nel momento in cui la finestra principale viene attivata, torna visibile il pulsante cmdTest.
E nel momento in cui la finestra perde lo stato attivo, il pulsante viene nascosto. Questo viene effettuato perché la gestione grezza dei messaggi fa sì che il nostro pulsante sia in primo piano pure quando la finestra che sembra contenerlo (in effetti esso è diventato finestra autonoma) passa in secondo piano. Prima di provare il programma scriviamo queste semplici righe di codice per il secondo form di nome Form1.
La sua unica funzione è quella di chiudere il form nel momento in cui l'utente clicca sul pulsante Command1. Possiamo adesso provare il programma. All'esecuzione il pulsantino ? si sposterà alle stesse coordinate dei pulsanti di chiusura e ridimensionamento del form. In realtà sappiamo che è un trucchetto ottico e la situazione non sta proprio così.
Il click sopra il pulsantino fa apparire una finestra con un messaggio. La stessa cosa accade quando l'utente clicca il pulsante "Altro form" che mostra il secondo form. Quando il secondo form è in primo piano (e il primo form è disattivo) il pulsante sulla barra del titolo del primo sparisce. Basterà riattivare la finestra del primo form, anche senza chiudere il secondo form, per vedere ritornare presente il pulsante sulla barra del titolo. Sappiamo che è uno strano giochetto, ma è stato reso necessario
per evitare spiacevoli comportamenti del pulsante. |
Questo genere di operazioni potrà sembrare molto carino, anche se un po' complesso, ma nasconde decine di insidie. In ogni caso non fermare mai l'esecuzione del programma tramite la voce
Interrompi dell'IDE di Visual Basic,
oppure attraverso l'istruzione END. Ciò lascerebbe l'hook in memoria
e la Window Procedure non verrebbe rimessa al suo posto. In definitiva le operazioni di subclassing devono essere adoperate il meno possibile e prima di provare i progetti che le utilizzano salvare tutti i lavori. Particolare cura deve essere data pure alle operazioni di chiusura del programma, in modo da sganciare tutti gli hook eseguiti all'avvio del programma. Moreno
Sirri
|
Torna all'indice degli HowTo |