Guide e Tutorials:indexed | ||||||
Drag&Drop (Articolo completo) di Giorgio Abraini Introduzione La gestione del trascinamento di un file da Esplora Risorse (o programma analogo) a una nostra applicazione scritta in Visual Basic va gestita tramite le API: infatti si tratta di creare una shell, ovvero un programma che interagisce con altre applicazioni, e non semplicemente di far interagire diversi controlli appartenenti ad una stessa applicazione. E' necessario quindi interfacciarsi ad un livello un po' più basso col sistema operativo. Il meccanismo attraverso cui operare, per sommi capi, è questo: un'applicazione che vuole poter ricevere i file trascinati da Esplora Risorse deve essere registrata a questo scopo; in altre parole, il sistema operativo deve sapere quali finestre sono autorizzate ad essere destinatarie di un'operazione di trascinamento di un file, e per avere l'autorizzazione dal sistema operativo bisogna registrarsi con un'apposita funzione DragAcceptFiles. Quando l'utente trascina uno o più file su una finestra autorizzata a riceverli, Windows invierà un messaggio a quella finestra per avvertirla che è stata compiuta un'operazione per la quale è autorizzata a rispondere. Pertanto l'applicazione a cui appartiene quella finestra (cioè l'applicazione che stiamo costruendo noi) deve intervenire per sapere quali file sono stati trascinati, e per ottenere queste informazioni dovrà utilizzare un'altra funzione apposita DragQueryFile. Quando tutti i file trascinati saranno stati processati dalla nostra applicazione, potremo chiudere definitivamente l'operazione di trascinamento grazie alla funzione DragFinish. Quali API utilizzare e quale progetto realizzare Le API necessarie per implementare il drag & drop, come accennato, sono sostanzialmente tre: DragAcceptFiles, DragQueryFile e DragFinish, tutte appartenenti alla libreria "shell32.dll". In più, saranno utilizzate anche la API DragQueryPoint e le API relative al subclassing di una finestra, per poter intercettare il messaggio inviato da Windows per comunicare l'operazione di trascinamento. Per mostrare come implementare il drag & drop di file si potrebbe banalmente realizzare un progetto "EXE standard", ma ritengo più utile costruire una DLL ActiveX che sfrutti la tecnica callback, in modo che possa essere riutilizzabile senza dover ogni volta riscrivere o riadattare il codice. La spiegazione dell'implementazione del subclassing e del callback non rientra negli scopi di questo articolo, per cui se siete completamente a digiuno vi conviene dare un'occhiata ai due paragrafi seguenti, in cui si accennerà qualche spiegazione; comunque non sono conoscenze indispensabili per la realizzazione della nostra DLL, anche perché è possibile ottenere lo stesso obiettivo senza ricorrere né al subclassing né al callback: tuttavia questo è il metodo che mi sembra più logico e conveniente. Il subclassing Il subclassing è una tecnica che consiste nell'intercettare i messaggi che il sistema operativo Windows spedisce alle finestre attualmente utilizzate dal sistema: per "finestra" qui si intende qualunque componente visualizzato da Windows che sia dotato di un handle. Anche un pulsante, un TextBox, un'etichetta sono "finestre" e possono ricevere messaggi. Ad ogni finestra è associata una procedura (una window procedure) che elabora i messaggi spediti dal sistema a quella specifica finestra: con Visual Basic 6 è possibile, utilizzando l'operatore AddressOf, sostituire a questa window procedure una nostra procedura che elabori opportunamente i messaggi. In questo modo è possibile interagire a un livello molto più profondo con il sistema operativo, e le potenzialità di questa tecnica sono vastissime. La sequenza delle operazioni da eseguire per ottenere il subclassing di una finestra è la seguente: 1) Avvertire Windows che la window procedure di una certa finestra (identificata dal suo handle) è sostituita da una nostra routine: per fare ciò abbiamo bisogno dell'Api SetWindowLong; 2) Creare la nuova window procedure che esamini i messaggi e prenda gli opportuni provvedimenti: questi possono consistere nell'esecuzione di particolari istruzioni se si intercettano determinati messaggi, oppure semplicemente nel "passare la palla" a Windows, che provvederà per conto suo a elaborare il messaggio; infatti la nostra window procedure non deve necessariamente elaborare TUTTI i messaggi spediti a una certa finestra, perché magari a noi interessano solo quelli relativi al mouse; tutti gli altri possono essere "rispediti al mittente", cioè a Windows, con l'Api CallWindowProc; 3) Avvertire Windows che la window procedure della finestra subclassata è di nuovo quella originaria: questo si fa solitamente quando la finestra sta per essere distrutta, in modo da ripristinare la situazione precedente; altrimenti ci potrebbero essere notevoli problemi. Se questi brevi cenni non vi hanno soddisfatto, potete fare riferimento al relativo articolo di F. Polesello [...] e al progetto adHook di F.Zanetta [...]. Il callback La tecnica del callback consente di gestire la notifica di d eterminati eventi o situazioni da un componente all'applicazione che lo utilizza. Ad esempio, supponiamo di aver creato un controllo ocx che spedisca un'e-mail; in questo, come in numerosi altri casi, è desiderabile che l'applicazione che sta utilizzando il controllo sia avvertita quando l'e-mail è stata spedita con successo. Ci sono vari modi per raggiungere questo scopo, e uno di questi è proprio il callback: in particolare, è possibile implementare il callback utilizzando eventi oppure funzioni. Nel caso particolare che stiamo analizzando è più conveniente utilizzare una funzione, perché l'evento può essere generato solo da una classe, mentre il punto in cui si verifica la situazione da notificare (ovvero il trascinamento di file) si trova nella nostra window procedure, che necessariamente deve trovarsi in un modulo standard. Per questo ho preferito abbandonare questa strada per usare una funzione callback. In pratica quello che succede è questo: 1) L'applicazione crea un'istanza di una classe (chiamiamola "classe1") che implementa l'interfaccia fornita dal componente per la funzione callback; 2) L'applicazione avvia il subclassing di una certa finestra comunicando al componente il riferimento all'istanza della "classe1"; 3) Il componente monitora i messaggi inviati alla finestra subclassata e, quando intercetta il messaggio relativo al trascinamento dei file, richiama la funzione callback della "classe1"; 4) L'applicazione gestisce nel modo che ritiene più opportuno la notifica dell'avvenuto trascinamento. Se anche questi brevi cenni non vi hanno soddisfatto, consultate l'MSDN, che è abbastanza chiara in proposito. Costruzione della DLL Ora possiamo cominciare a costruire la nostra DLL: abbiamo bisogno di due classi e di un modulo. Una classe, che chiameremo clsSubClass, serve ad abilitare una finestra (scelta dall'applicazione client) a ricevere i file trascinati da Esplora Risorse, e ad avviare il subclassing di tale finestra; la seconda classe, che chiameremo clsDrop, è quella con la funzione callback che servirà a notificare l'evento "file trascinati"; infine, il modulo ("modSubClass") è necessario per creare la routine che prenderà il posto della window procedure della finestra da subclassare, e naturalmente lo useremo anche per tutte le dichiarazioni relative alle API. Cominciamo dalla classe più semplice, la clsDrop: questa non deve contenere altro che la funzione callback:
Questa funzione comunica il nome del file trascinato, e le coordinate del mouse nel punto in cui è stato rilasciato il pulsante sinistro: infatti è in questo momento che avviene il "drop". Se i file trascinati sono più di uno, la funzione callback verrà chiamata fino ad esaurire tutti i file, come vedremo tra poco. Notate che la funzione non contiene codice: il codice va scritto solo nella classe dell'applicazione che implementa clsDrop, perché è l'applicazione che deve gestire opportunamente l'evento, non è la classe in se stessa che deve farlo. Anche la classe clsSubClass deve contenere una sola funzione (che ho chiamato "DropFiles"), che riceva come parametro l'handle della finestra che deve fare da destinazione del trascinamento dei file e che deve pertanto essere subclassata. Per abilitare una finestra a ricevere i file trascinati bisogna usare, come si è detto, l'API DragAcceptFiles: essa accetta come parametri l'handle della finestra destinataria del trascinamento e un booleano che indica se tale finestra può o non può accettare i file trascinati. In altre parole, il booleano determina se la finestra deve essere inclusa o esclusa dall'elenco delle finestre abilitate a ricevere i file trascinati. Naturalmente il primo parametro, l'handle della finestra, è un Long (per le dichiarazioni esatte guardate il modulo incluso nel progetto). La funzione DragAcceptFiles non restituisce alcun valore. Dal momento che DragAcceptFiles vuole sapere se abilitare o disabilitare la finestra alla ricezione dei file, anche la nostra funzione DropFiles deve fare la stessa cosa, e quindi accetterà, come secondo parametro, un Booleano. Se questo parametro è True, una volta registrata la finestra, dobbiamo subclassarla con l'API SetWindowLong, la quale come si è accennato non fa altro che sostituire alla window procedure della finestra la routine che indicheremo noi; essa restituirà l'indirizzo della vecchia window procedure, che dobbiamo memorizzare sia per ripristinare la situazione iniziale quando ce ne sarà bisogno, sia per far processare a Windows i messaggi che non ci interessano; tale valore sarà anche quello restituito dalla nostra funzione DropFiles. Se invece il booleano è False, dobbiamo deregistrare la finestra e annullare il subclassing ripristinando la situazione originaria, sempre usando le API DragAcceptFiles e SetWindowLong. La funzione sarà pertanto siffatta:
Le spiegazioni sul parametro Classe e il suo utilizzo seguiranno tra breve; ora occupiamoci del modulo: esso deve contenere, oltre a tutte le dichiarazioni relative alle API utilizzate e alle altre variabili impiegate, la nuova window procedure da assegnare alla finestra da subclassare. In tale routine dobbiamo intercettare un solo messaggio: WM_DROPFILES (&h233), che è precisamente quello spedito da Windows (anzi, da Esplora Risorse) alla finestra registrata con DragAcceptFiles: quando l'utente trascina un file da Esplora Risorse alla finestra della nostra applicazione, Esplora Risorse controlla che la finestra sia abilitata a ricevere il messaggio; se lo è, memorizza in un'apposita struttura le informazioni sui file trascinati e spedisce il messaggio, altrimenti fa finta di nulla e la finestra non riceverà alcun messaggio. La nostra window procedure, come quella di default, avrà come parametri l'handle della finestra subclassata, il numero intero a 32 bit identificativo del messaggio, e altri due Long che determinano informazioni aggiuntive per quel parametro: di questi ultimi due noi useremo solo il primo, wParam, che rappresenta l'handle della struttura con le informazioni sui file; tuttavia anche il secondo parametro, lParam, deve essere presente nella dichiarazione, affinché il subclassing funzioni correttamente. Accertato che il messaggio intercettato è quello che ci interessa, dobbiamo quindi recuperare le informazioni sui file trascinati; queste informazioni sono sostanzialmente due: il nome dei file e le coordinate del punto in cui il pulsante sinistro del mouse è stato rilasciato sulla finestra, generando l'invio del messaggio. Il recupero delle coordinate del punto è molto semplice: basta usare l'API DragQueryPoint passandogli come parametri l'handle della struttura e una variabile di tipo POINTAPI, che ha come membri due long corrispondenti alle coordinate X e Y del punto. Attenzione al fatto che le coordinate sono in pixel, e all'eventuale conversione in twips. Per i nomi dei file bisogna invece usare DragQueryFile, che ha bisogno di quattro parametri: il solito handle della struttura, l'indice del file di cui vogliamo sapere il nome (infatti i file potrebbero essere più di uno), una stringa che sarà riempita col nome (e anche col path COMPLETO) del file e la dimensione in byte della stringa. Quest'ultimo parametro è necessario perché naturalmente la stringa viene passata per riferimento, e la API ha bisogno di sapere quanto è lunga per evitare una fastidiosa sovrascrittura della zona di memoria adiacente alla stringa. Ma come si fa a sapere quanti file sono stati trascinati? Si usa sempre la stessa funzione, indicando come indice del file il valore -1: in tal caso infatti DragQueryFile restituirà il numero di file trascinati, mentre se l'indice del file è compreso tra 0 e tale numero, DragQueryFile restituirà il numero di byte copiati nella stringa che le abbiamo passato. Questo valore ci tornerà particolarmente utile in quanto la stringa sarà riempita in "stile C", ovvero sarà terminata da un byte nullo. Quanto alla stringa e alla sua dimensione, si può scegliere se usare una stringa a lunghezza fissa o una a lunghezza variabile; tenendo conto che la stringa deve contenere il percorso e il nome del file, e che la lunghezza del percorso può cambiare notevolmente da caso a caso, sembra più conveniente la seconda soluzione, che permette una maggiore flessibilità, eventualmente facendo in modo che sia l'applicazione client a determinare quanto spazio riservare alla stringa. Ottenute le informazioni che volevamo, possiamo richiamare la funzione callback per avvertire l'applicazione client che si è verificato il trascinamento di uno o più file. A questo punto ci sono due possibilità: chiamare la funzione una volta sola comunicando il nome di tutti i file trascinati, oppure chiamare la funzione tante volte quanti sono i file; a me è sembrato più semplice scegliere la seconda strada, ma più che altro è questione di gusti. Volendo si potrebbe anche comunicare semplicemente il numero di file trascinati e mettere a disposizione dell'applicazione una funzione che recuperi il nome di ciascun file, in modo che sia l'applicazione a determinare come e quando ottenere questa informazione: sono tutte modifiche relativamente semplici da implementare. Ad ogni modo, per chiamare la funzione callback la nostra DLL ha bisogno di conoscere l'istanza della classe dell'applicazione client che implementa la classe clsDrop: proprio a questo serve il parametro Classe della funzione DropFiles, che non a caso è del tipo clsDrop. L'applicazione, come si vedrà tra poco, deve creare un'istanza di una classe che implementi clsDrop e passare il riferimento di questa istanza alla funzione DropFiles, in modo che la DLL sappia che deve richiamare la funzione callback di QUELLA istanza. ; poiché è il modulo che deve effettuare il callback, esso deve avere un riferimento ad una classe di tipo clsDrop, riferimento che è opportunamente impostato nella funzione DropFiles, come si può notare sopra. Terminata l'elaborazione dell'evento drop per tutti i file, occorre liberare la memoria occupata all'inizio dell'operazione di trascinamento: per questo è sufficiente chiamare l'API DragFinish passando come parametro l'handle della struttura da "pulire". In definitiva, questa è la nostra window procedure personalizzata:
Le variabili utilizzate da questa routine (FileName, hDrop, nFiles, ecc.) potevano essere dichiarate localmente, ma dal momento che la window procedure viene richiamata un'infinità di volte (ogni volta che una finestra riceve un messaggio!), sembra un vero spreco dover creare ogni volta tutte queste variabili; per questo ho preferito dichiararle a livello di modulo. La nostra DLL è praticamente pronta: non resta che metterla alla prova. Il test della DLL Per testare la DLL è sufficiente creare un banale progetto "EXE standard": sul form ci piazziamo un bel textbox, e inoltre aggiungiamo un modulo di classe che chiamiamo "pDrop" e che deve implementare la classe clsDrop; il progetto EXE standard deve pertanto avere un riferimento al progetto della DLL (o alla DLL vera e propria, se l'avete compilata) dopodiché si può scrivere:
nella sezione delle dichiarazioni di pDrop. Ora in questa classe sarà disponibile la routine DropEvent, ovvero la routine callback che sarà chiamata dalla DLL, e nel corpo della routine possiamo scrivere il codice che gestirà l'evento "drop", ad es. scrivendo sul textbox il nome dei file trascinati:
Nel form bisognerà dichiarare, a livello di modulo, un riferimento alla classe pDrop e uno alla classe clsSubClass: nella routine Form_Load (o nella routine click di un pulsante, o in un qualunque altro luogo vi vada a genio) bisognerà creare le istanze di queste due classi e richiamare la funzione DropFiles passando come parametri l'handle del textbox e il riferimento alla classe pDrop. La DLL provvederà a subclassare il textbox e a richiamare la routine DropEvent dell'istanza della classe pDrop, la quale a sua volta visualizzerà il nome dei file trascinati. Quando l'applicazione verrà terminata (e quindi nell'evento unload del form), si chiamerà di nuovo la funzione DropFiles per interrompere il subclassing e si distruggeranno le istanze delle due classi. Conclusioni L'implementazione del drag & drop di file richiede una stretta interazione col sistema operativo: quando un utente trascina un file su una finestra, questa (se è abilitata ad accettare i file) riceverà il messaggio WM_DROPFILES, insieme all'handle di una struttura che contiene i nomi dei file trascinati e la posizione del mouse nel momento in cui l'utente ha rilasciato il pulsante sinistro. Per avere accesso a tale struttura, quindi, è necessario registrare la finestra tra quelle abilitate a ricevere i file trascinati, intercettare il messaggio WM_DROPFILES e successivamente estrapolare dalla struttura le informazioni che interessano. Per porre in atto questo meccanismo è conveniente creare un componente esterno, come una DLL ActiveX, che sfruttando le tecniche del subclassing e del callback avverta l'applicazione client che lo sta utilizzando del momento in cui avviene il trascinamento dei file, comunicandole le informazioni rilevanti in proposito, ovvero il nome dei file e la posizione del mouse. Così com'è la DLL permette di usare contemporaneamente numerose finestre come destinatarie del trascinamento di file, ma una sola istanza della classe pDrop può rispondere alla chiamata della funzione callback: il motivo è che il modulo possiede un solo riferimento a una classe di tipo clsDrop. Il superamento (o l'aggiramento) di questo limite è piuttosto semplice da realizzare: una possibile soluzione (probabilmente la più conveniente) consiste nell'includere, tra i parametri della funzione callback, l'handle della finestra che ha ricevuto il messaggio WM_DROPFILES, in modo che l'applicazione client possa eseguire istruzioni differenti a seconda del valore di questo parametro. Biografia Win32 Programmer's Reference MSDN Library 6.0 |
Archivio:ndexed | ||||
Lezioni | Commenta questa lezione | Invia la tua guida | Avviso per le nuove lezioni | Proponi un argomento |
Visual Basic Italia© copyright 2000 - tutti i diritti riservati |