MokaByte
Numero 23 - Ottobre 98
|
di Marco Molinari
|
in Java
|
|
Uno degli elementi che danno più intuitività a un'interfaccia grafica è il drag&drop, la possibilità di prendere e trascinare oggetti grafici in giro per lo schermo ottenendo risultati diversi a seconda di dove li si lascia cadere. Uno degli utilizzi più comuni di questo meccanismo è la possibilità di copiare o spostare dei file trascinandoli da una finestra all'altra, ma molti altri software lo utilizzano: molti editor di testo, ad esempio, permettono di spostare un brano evidenziandolo e trascinandolo in un'altra locazione; i programmi di grafica si appoggiano al drag&drop per manipolare le immagini. Il Drag & Drop Questo articolo descrive l’API drag&drop (d’ora in avanti d&d) introdotta nel jdk 1.2; essa permetterà di utilizzare il d&d per scambiare dati tra applicazioni Java nella stessa Virtual Machine, tra applicazioni Java in Virtual Machine diverse e tra le applicazioni Java e la piattaforma nativa. L’API dovrebbe essere diventata abbastanza stabile da poterla descrivere senza il pericolo di vedere grossi stravolgimenti, come quello che è avvenuto tra la beta 3 e la beta 4 del jdk 1.2. Vedremo come funziona il meccanismo, come è descritto nella API, le sue limitazioni intrinseche e il suo utilizzo in pratica. Infine vedremo lo stato attuale delle cose nel jdk 1.2 beta 4. Cosa è il Drag&Drop Durante una azione di d&d si possono distinguere tre elementi:
Una destinazione, anch’essa associata a un elemento grafico, capace di ricevere dei dati da una sorgente Un utente umano che esegue il gesto di prendere, trascinare e lasciare dalla sorgente alla destinazione Gli eventi sono vari, più di quanti uno può immaginarsi all’inizio. Si pensi a quando si trascina un file da una finestra all’altra: quando si inizia a trascinare l’icona il puntatore "si porta dietro" l’icona e cambia di forma (sotto Windows diventa un simbolo di divieto, perchè non ha senso spostare un file nella propria directory); mentre si passa sopra un’altra finestra il puntatore può assumere forme diverse a seconda dell’esito che avrebbe l’operazione (spostamento o copia): inoltre se si passa sopra all’icona di una directory questa si evidenzia (per indicare che il file verrebbe copiato lì dentro); c’è anche una funzione di autoscroll, che fa in modo che avvicinandosi al bordo la finestra scrolli in quella direzione. Infine una volta che si lascia il tasto del mouse avviene una certa azione: in generale si può dire che avviene uno spostamento, una copia o un collegamento. Mentre le prime due sono chiare, l’azione collegamento non è univocamente definita ma dipende dalla piattaforma e dalla applicazione; un esempio è quando si crea un collegamento a un file o a un URL. java.awt.dnd.DragSource Si noti come il d&d non sia inserito nelle Swing, anche se è considerato parte delle JFC. Questa distinzione è ovvia se si considera che le Swing sono 100% Pure Java mentre per forza di cose il d&d deve basarsi su una certa quantità di codice nativo. Le sorgenti di trascinamento sono forse l’elemento meno intuitivo dell’API. Gli elementi fondamentali sono:
un oggetto che implementi Transferable, ovvero, come vedremo dopo, capace di fornire i dati in un certo formato a una destinazione; un oggetto che implementi DragGestureListener, che sarà il primo ad "accorgersi" dell’inizio di un’azione di d&d; un DragSource, che si occupa di iniziare il d&d; un DragGestureRecognizer, che traccia e identifica ogni gesto di trascinamento compiuto dall’utente. Innanzitutto bisogna ottenere un oggetto DragSource; questo oggetto si può ottenere in vari modi ma di base ogni vm ne ha uno per tutta la durata della propria vita. DragSource ds = DragSource.getDefaultDragSource(); Ottenuto questo, il Component lega a sè stesso a un DragGestureRecognizer; questa è una classe astratta che incapsula ogni tipo di dipendenza che c’è tra i componenti, le piattaforme e le periferiche di ingresso. Un DragGestureRecognizer standard può essere ottenuto da DragSource, da ToolKit o da altre parti. Sui PC il DragGestureRecognizer standard si basa sul mouse ed è una implementazione concreta della MouseDragGestureRecognizer, ma non è detto che su altre piattaforme sia così. Al DragGestureRecognizer si legano anche le azioni che si possono compiere sul componente (copy, move, link) e un DragGestureListener; quest’ultimo riceverà dal DragGestureRecognizer un DragGestureEvent quando l’utente inizierà un’azione di drag. ds.createDefaultDragGestureRecognizer(sourceComponent, actions, dragGestureListener); DragGestureListener è una interfaccia che definisce un solo metodo, dragGestureRecognized(DragGestureEvent dge), che solitamente non farà altro che richiamare il metodo startDrag. Con startDrag il DragSource (il gestore unico delle operazioni) inizia a controllare le azioni dell’utente; DragSource si preoccupa che in un dato istante ci sia una sola azione di drag&drop. Con startDrag si dovrebbe specificare soprattutto l’oggetto Transferable; si può anche legare un DragSourceListener, che viene avvisato di quello che sta avvenendo (entrata, uscita, passaggio in un target e altro) e dovrebbe fornire effetti visivi di trascinamento all’utente, per esempio cambiando la forma del cursore a seconda che sia sopra una destinazione valida o no. Inoltre si può specificare al DragSource un’immagine per l’oggetto del trascinamento e altre cose ancora meno fondamentali. Riassumiamo in punti quello che di solito accade in una sorgente di drag:
java.awt.dnd.DropTarget Come ci si può aspettare una destinazione è completamente a sè stante rispetto a una sorgente: una destinazione non sa da chi arriva il dato, lo riceve e basta; se vuole può sapere se arriva dall’interno della vm o dall’esterno. Gli elementi fondamentali per creare una destinazione sono:
Transferable Risolto questo c’è lo scambio vero e proprio di dati. La chiave di tutto è l’interfaccia Transferable di java.awt.Datatransfer. Gli oggetti che la implementano sono in grado di fornire una lista di formati supportati (i Data Flavor) e anche di fornire il dato stesso in un certo formato richiesto sotto forma di generico Object. Quest’ultima funzionalità è fornita dal metodo getTransferData. Il trasferimento di dati tra una applicazione Java e l’ambiente nativo è più complesso. Internamente Java usa dei tipi MIME incapsulati dentro un DataFlavor per rapprensentare i suoi tipi, mentre le varie piattaforme usano rappresentazioni diverse. Per questo c’è bisogno di un oggetto, la FlavorMap (e la sua implementazione standard SystemFlavorMap) per mappare i tipi nativi in tipi MIME interni alla VM e viceversa. Quando si tenta uno scambio esso può avvenire se e solo se i partecipanti sono d’accordo sul tipo di dato e sulla sua codifica. Quindi in pratica i formati "nativi" della piattaforma vanno implementati a mano, in quanto l’API fornisce solo una inputstream. Fortunatamente ci sarà un meccanismo dedicato che permetterà di ottenere una lista di file dall’ambiente nativo come List (contenitore astratto del jdk 1.2) di File. Un po’ di codice Come esempio riporto una versione modificata dell’esempio della prima Question of the Week sul sito di Sun (vedi bibliografia). Si apre una semplice finestra con un TextField e una Label, e si può trascinare il testo del primo nel secondo. In questo caso il TextField implementa tutte le interfaccie che girano attorno alle sorgenti (DragGestureListener, DragSourceListener e Transferable) e la Label implementa DropTargetListener. Le modifiche interessanti che ho fatto sono due: il TextField, che anche originariamente implementava DragSourceListener, viene legato al DragSource e fornisce informazioni sull’oggetto trascinato (stranamente nell’esempio originale questo non avveniva); la Label è stata modificata in modo da correggere un problema con le sorgenti dalla piattaforma nativa. Purtroppo il d&d è stato disattivato nella beta 4 del jdk 1.2 per Solaris, quindi questo codice è compilabile solo sotto Windows. Provando questo programma si noteranno dei problemi: per esempio accade spesso che ci sia una pausa di anche 5 secondi prima che l’azione avvenga. Inoltre succede ogni tanto che l’evento non venga visto del tutto. Può essere utile testare il programma senza attivare il JIT. Tirando le somme Quello che segue non vuole essere una critica, ma solo un rapporto sull’effettivo stato dei lavori per chi è interessato al d&d: il jdk 1.2 è ancora in beta, e dichiaratamente non è un prodotto finito: il d&d è un chiaro esempio di questo. Chi si vuole avvicinare a questa API adesso dovrebbe farlo mettendosi nello spirito dell’esploratore ed essere ben conscio che le specifiche non sono ancora stabili, anche se non dovrebbero essere cambiate molto in futuro. Chi ha provato a cimentarsi con il d&d con jdk1.2 beta 3 si è trovato a dover modificare i programmi in maniera sostanziale con la beta 4 e la discussione per definire l’API sono ancora aperte. In pratica attualmente il d&d non funziona sotto Solaris: è stato disattivato nella beta 4 per gli eccessivi problemi che si avevano. Sotto Windows il d&d funziona con i limiti che abbiamo visto all’interno della virtual machine, ma sotto 95 il trasferimento dei dati da ambiente ospite a vm non funziona. Questo vuol dire che tutti gli eventi vengono generati e riconosciuti, ma che poi non è possibile portare a termine un trasferimento di dati. Sotto NT invece questo trasferimento funziona. Però ci si può aspettare una buona dose di "errori strani", magari inspiegabili, dovuti sempre al carattere di beta dell’API. In ogni caso è attesa la nuova versione del jdk che dovrebbe risolvere buona parte di questi problemi. Bibliografia La maggiore fonte di informazioni sul d&d è ancora il testo delle specifiche; inoltre si possono trovare informazioni interessanti andando a cercare nell’archivio delle domande e dei bug, inserendo come argomento "drag&drop". |
MokaByte Web 1998 - www.mokabyte.it MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it |