Advanced   Java   Services Drag and Drop Back Next Up Home


Drag und Drop mit dem TransferHandler

In der Version 1.4 wurde die Klasse Transferhandler eingeführt. Die Klasse erleichtert es, Daten aus Swinganwendungen mit Hilfe von Drag und Drop oder über das ClipBoard zu übertragen. Die Klasse Transferhandler kann direkt verwendet werden, für spezielle Fälle kann man die Klasse auch ableiten und sich so einen eigenen Transferhandler schreiben.

Sie wird verwendet in zwei Methoden, die ebenfalls seit der Version 1.4 in der Basisklasse JComponent ergänzt wurden, setTransferHandler(TransferHandler newHandler) und TransferHandler getTransferHandler()

Für die meisten Swingkomponenten liefert getTransferHandler() null. Einige Swingkomponenten haben jedoch einen DefaultTransferHandler vorinstalliert. Es sind dies die folgenden sechs Swingkomponenten.

Bei diesen Komponenten ist der DefaultTransferHandler jedoch zunächst abgeschaltet. Es gibt in diesen Klassen eine eigene Methode, mit der man ihn aktivieren kann: setDragEnabled(boolean enable) Die folgende Tabelle bringt eine Übersicht über die internen TransferHandler dieser Komponenten

KomponenteDefaultTransferHandlerist nested class in
JColorChooserColorTransferHandlerBasicColorChooserUI
JFileChooserFileTransferHandlerBasicFileChooserUI
JListListTransferHandlerBasicListUI
JTableTableTransferHandlerBasicTableUI
JTreeTreeTransferHandlerBasicTreeUI
JTextComponentTextTransferHandlerBasicTextUI

Zunächst ein Beispiel, indem wir einen DefaultTransferHandler benützen. Wir wollen die Texteinträge einer JList in eine andere JList per drag und drop übertragen.


Drag aus JList mit dem (Default)ListTransferHandler

Mit einer einzigen Zeile aktivieren wir den Standard-ListTransferHandler, sie ist im folgenden Beispiel rot gezeichnet.

import java.awt.*;
import java.util.*;
import javax.swing.*;

public class JListDragDemo extends JFrame
{
   private Container conPane ;
   private JList jList;
   private JScrollPane sp ;
   private Vector list;

   public JListDragDemo()
   {
      conPane = getContentPane();
      initCenter();
      initFrame();
   }

   // ----------------------------- initCenter ----------------------------- \\
   private void initCenter()
   {
      list = new Vector();
      list.add("one");
      list.add("two");
      list.add("three");
      list.add("four");
      list.add("five");
      list.add("sex");
      jList = new JList(list);
      jList.setDragEnabled(true);
      sp = new JScrollPane(jList);
      conPane.add(sp, BorderLayout.CENTER);
   }

   // ------------------------------ initFrame ------------------------------ \\
   private void initFrame()
   {
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
      setTitle(" JListDragDemo");
      setSize(300,400);
      setLocation(47,47);
      setVisible(true);
   }

   public static void main(String[] args)
   {
      JListDragDemo wnd = new JListDragDemo();
   }

} // end class

Drop in eine JList mit dem (Default)ListTransferHandler

Für drop muß ein wenig mehr getan werden. Im wesentlichen läuft es darauf hinaus, die Daten, die aufgenommen werden sollen, auf Gültigkeit zu untersuchen. Dazu braucht man einen DropTargetListener. Ein Objekt der Klasse DropTarget stellt dann die Verbindung her zwischen den anliegenden Daten und der JList. Es wird meist mit dem folgenden Konstruktor instanziiert:

Das folgende Beispiel implementiert die drop-Seite für eine JList. Entscheidende Passagen sind rot eingefärbt.

import java.io.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import java.awt.datatransfer.*;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;

public class JListDropDemo extends JFrame implements DropTargetListener
{
   private Container conPane ;
   private JList jList;
   private JScrollPane sp ;
   private Vector list ;

   public JListDropDemo()
   {
      conPane = getContentPane();
      initCenter();
      initFrame();
   }

   // ----------------------------- initCenter ----------------------------- \\
   private void initCenter()
   {
      list = new Vector();
      list.add("eins");
      list.add("zwei");
      list.add("drei");
      list.add("vier");
      list.add("fünf");
      list.add("sex");
      jList = new JList(list);
      sp = new JScrollPane(jList);

      jList.setDragEnabled(true);
      //DropTarget dropTarget = new DropTarget(component, dropTargetListener);
      DropTarget dropTarget = new DropTarget (jList, this);

      conPane.add(sp, BorderLayout.CENTER);
   }

   // ------------------------------ initFrame ------------------------------ \\
   private void initFrame()
   {
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
      setTitle(" JListDropDemo");
      setSize(300,400);
      setLocation(397,47);
      //setResizable(false);
      setVisible(true);
   }


   // -------------- vom DropTargetListener gefordete methoden -------------- \\

   public void dragEnter(DropTargetDragEvent e)
   { }

   public void dragExit(DropTargetEvent e)
   { }

   public void dragOver(DropTargetDragEvent e)
   { }

   public void drop(DropTargetDropEvent e)
   {
      try
      {
         Transferable tr = e.getTransferable();

         if ( tr.isDataFlavorSupported (DataFlavor.stringFlavor) )
         {
            e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
            String data = (String)tr.getTransferData(DataFlavor.stringFlavor);
            // Sind in der Quelliste mehrere Elemente ausgewählt, so werden diese in
            // einem String übertragen. Trennzeichen ist dann der Zeilenvorschub.
            StringTokenizer tokens = new StringTokenizer(data, "\n") ;
            while( tokens.hasMoreTokens() )
            {
               list.add( tokens.nextToken() );
            }
            jList.updateUI();
            e.getDropTargetContext().dropComplete(true);
         }
         else
         {
            System.err.println ("DataFlavor.stringFlavor is not supported, rejected");
            e.rejectDrop();
         } 
      }
      catch (IOException ex)
      {
      }
      catch (UnsupportedFlavorException ex)
      {
         System.err.println ("UnsupportedFlavorException");
         ex.printStackTrace();
         e.rejectDrop();
      }

   }

   // -------------------------- dropActionChanged -------------------------- \\
   public void dropActionChanged(DropTargetDragEvent e)
   { }

   public static void main(String[] args)
   {
      JListDropDemo wnd = new JListDropDemo();
   } // end main
}

Mit event.getTransferable() erhält man ein Transferable-Objekt. Mit isDataFlavorSupported() fragt man, ob die anliegenden Daten einen passenden Typ haben. Mit getTransferData() holt man anschließend die Daten und nimmt sie in die eigene Liste auf. Will man eine JList, die Drag und Drop beherrscht, so muß man die beiden Beispiele vereinigen.


Drag von JList nach Windows (Desktop, Explorer, Arbeitsplatz)

Unser nächstes Ziel ist es, eine JList zu entwerfen, mit der man per Drag und Drop Dateien kopieren kann. Wir wollen Einträge in der JList etwa in ein Arbeitsplatzfenster des Betriebssystems übertragen. Dazu müssen wir uns einen eigenen TransferHandler schreiben. des weiteren müssen wir eine eigene Transferableklasse schreiben, die die Daten enthält.

Man kann das Vorgehen in drei Schritte zerlegen.

Schritt drei kennen wir schon. Das folgende Beispiel demonstriert das Vorgehen.

Um den Blick auf das wesentliche nicht zu verstellen, wollen wir eine möglichst einfache graphische Oberfläche realisieren. Eine JList zeigt den Inhalt eines festen Verzeichnisses an. Die angezeigten Dateien sollen dann per Drag aus der JList heraus in ein Arbeitsplatzfenster kopiert werden können. Als erstes müssen wir


1) Das Interface java.awt.datatransfer.Transferable implementieren

Hierzu sind drei Methoden zu schreiben. Die wichtigste Methode ist public Object getTransferData(DataFlavor flavor). Sie liefert die Daten und wird vom TransferHandler gerufen. Beim Aufruf wird ein Objekt übergeben werden, das angibt, was für Daten gewünscht werden. Will man einen Export etwa in ein Arbeitsplatzfenster, so heißt das, daß man eine Datei dorthin kopieren will. Hier reicht es nicht, einen String zu liefern. Die Klasse DataFlavor stellt hierfür die statische Konstante DataFlavor.javaFileListFlavor bereit.

import java.io.*;
import java.util.*;
import java.awt.datatransfer.*;

public class TransferableFile implements Transferable
{
   private List fileList ;

   public TransferableFile(List files)
   {
      fileList = files;
   }

   // Returns an object which represents the data to be transferred.
   public Object getTransferData(DataFlavor flavor)
      throws UnsupportedFlavorException
   {
      if( flavor.equals(DataFlavor.javaFileListFlavor) )
         return fileList ;

      throw new UnsupportedFlavorException(flavor);
   }

   // Returns an array of DataFlavor objects indicating the flavors
   // the data can be provided in.
   public DataFlavor[] getTransferDataFlavors()
   {
      return new DataFlavor[] {DataFlavor.javaFileListFlavor} ;
   }

   // Returns whether or not the specified data flavor is supported for this object.
   public boolean isDataFlavorSupported(DataFlavor flavor)
   {
      return flavor.equals(DataFlavor.javaFileListFlavor) ;
   }
}

Hier wird im wesentlichen geprüft, ob man dem Aufrufer von getTransferData die gewünschte Sorte Daten übermitteln kann. Falls die Prüfung positiv ausfällt, werden die Daten geliefert. Außerdem werden Methoden bereitgestellt mit denen man feststellen kann, welche Art von Daten überhaupt geliefert werden können. In unserem Fall werden die zu liefernden Daten per Konstruktor übergeben. Daß man mit einer List arbeiten muß, entnimmt man der API. Die Dokumentation der Klasse DataFlavor enthält den folgenden Hinweis:

Nun müssen wir eine eigene TransferHandlerklasse schreiben, die unsere Klasse TransferableFile verwendet.


2) Die Klasse javax.swing.TransferHandler erweitern

Objekte dieser Klasse initieren den Dragmechanismus. Hier müssen die zwei Methoden getSourceActions() und createTransferable() überschrieben werden. Ersteres ist trivial.

public class ListTransferHandler extends TransferHandler
{
   private String dir;

   public ListTransferHandler()
   {
   }

   public ListTransferHandler(String dir)
   {
      if( dir.endsWith("/") )
         this.dir = dir;
      else
         this.dir = dir+"/";
   }

   public Transferable createTransferable(JComponent c)
   {
      JList list = (JList) c;  // we know it's a JList
      Object[] values = list.getSelectedValues();  // strings
      Vector files = new Vector();
      String listEntry ;
      for(int i=0; i<values.length; i++)
      {
         listEntry = (String)values[i];
         if( listEntry.startsWith("<dir>") )
            continue;

         listEntry = listEntry.substring(7);
         System.out.println(dir + listEntry);
         files.add( new File(dir + listEntry) );
      }
      TransferableFile  tf = new TransferableFile(files);
      return tf;
   }

   public int getSourceActions(JComponent c)
   {
      return COPY ;
   }

   /*
   public void exportToClipboard(JComponent comp, Clipboard clip, int action)
      throws IllegalStateException
   {
      System.out.println("exportToClipboard");
      super.exportToClipboard(comp,clip,action);
   }

   // Causes the Swing drag support to be initiated.
   public void exportAsDrag(JComponent comp, java.awt.event.InputEvent e, int action)
   {
      System.out.println("exportAsDrag");
      super.exportAsDrag(comp, e, action);
   }

   //Invoked after data has been exported.
   public void exportDone(JComponent source, Transferable data, int action)
   {
      System.out.println("exportDone");
      super.exportDone(source, data, action) ;
   }
   */

} // end class ListTransferHandler extends TransferHandler

Wer neugierig ist, sollte die auskommentierten Methoden verwenden, um festzustellen, welche Methoden in welcher reihenfolge aufgerufen werden. Schritt drei ist nichts neues.


3) setDragEnabled() und setTransferHandler() für JList einsetzen
JList fileList = new JList(fileVector);
fileList.setDragEnabled(true);  // siehe erste rote Zeile im Code von Punkt 4)
fileList.setTransferHandler( new ListTransferHandler(defaultDir) );
                               // siehe zweite rote Zeile im Code von Punkt 4)

Nun brauchen wir noch eine kleine graphische Oberfläche, in die wir unsere JList einbetten. Wie bereits erwähnt gehen wir von einem festen Verzeichnis "c:/source/" aus. Durch unsere Vorarbeit reichen hier die unter drittens aufgeführten Statements, um Drag zu aktivieren.


4) Graphische Oberfläche, die JList einsetzt
import java.io.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import java.awt.datatransfer.*;

public class DragDemo extends JFrame
{
   private Container conPane ;
   private JList dragFileLister;
   private JScrollPane sp ;
   private String sourceDir = "c:/source/";

   public DragDemo()
   {
      conPane = getContentPane();
      initCenter();
      initFrame();
   }

   // ----------------------------- initCenter ----------------------------- \\
   private void initCenter()
   {
      File source = new File(sourceDir) ;
      File[] files = source.listFiles() ;
      Vector fileVector = new Vector();
      for(int i=0; i<files.length; i++)
      {
         if ( files[i].isDirectory() )
            fileVector.add( "<dir>  " + files[i].getName());
         else
            fileVector.add("<file> "+ files[i].getName());
      }
      dragFileLister = new JList(fileVector);
      dragFileLister.setDragEnabled(true);
      dragFileLister.setTransferHandler( new ListTransferHandler(sourceDir) );
      sp = new JScrollPane(dragFileLister);
      conPane.add(sp, BorderLayout.CENTER);
   }

   // ------------------------------ initFrame ------------------------------ \\
   private void initFrame()
   {
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
      setTitle(" DragDemo   listing of " + sourceDir);
      setSize(300,400);
      setLocation(47,47);
      setVisible(true);
   }

   public static void main(String[] args)
   {
      DragDemo wnd = new DragDemo();
   }
}

Drop aus Windows(Desktop, Explorer, Arbeitsplatz) in JList

Nun wollen wir den umgekehrten Weg gehen. Wieder soll die graphische Oberfläche möglichst einfach sein. Wir wollen mit JList ein festes Verzeichnis, etwa "c:/target/" anzeigen. Nimmt man ein Arbeitsplatzfenster oder unser voriges Dragbeispiel, so soll es möglich sein, Dateinamen in die JList per Drop aufzunehmen. Es sollen dabei nicht nur die Namen in die JList übertragen werden, es soll die Datei auch physikalisch in das Verzeichnis kopiert werden. Unser Vorgehen ist ganz analog dem ersten Drop-Beispiel Drop in eine JList mit dem (Default)ListTransferHandler. Wir müssen einen DropTargetListener installieren und die zentrale Methode public void drop(DropTargetDropEvent e) entsprechend den neuen Anforderungen anpassen.

Hier das vollständige Listing des Programms. Entscheidend ist die neue Implementierung der Methode public void drop(DropTargetDropEvent e). In ihr wird nicht nur die JList aktualisiert, sondern auch Kopieren der Datei(en) erledigt. Die methode ist im folgenden rot eingefärbt.

import java.io.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.datatransfer.*;

import java.awt.dnd.DropTarget;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;


public class DropDemo extends JFrame implements DropTargetListener
{
   private Container conPane ;
   private Vector fileVector;
   private JList dropFileList;
   private JScrollPane sp;
   private String targetDir = "c:/target/";

   public DropDemo()
   {
      conPane = getContentPane();
      initCenter();
      initFrame();
   }

   // ----------------------------- initCenter ----------------------------- \\
   private void initCenter()
   {
      File source = new File(targetDir) ;
      File[] files = source.listFiles() ;
      fileVector = new Vector();
      for(int i=0; i<files.length; i++)
      {
         if ( files[i].isDirectory() )
            fileVector.add( "<dir>  " + files[i].getName());
         else
            fileVector.add("<file> "+ files[i].getName());
      }

      dropFileList = new JList(fileVector);

      dropFileList.setDragEnabled(true);
      DropTarget dropTarget = new DropTarget (dropFileList, this);

      sp = new JScrollPane(dropFileList);
      conPane.add(dropFileList, BorderLayout.CENTER);
   }

   // ------------------------------ initFrame ------------------------------ \\
   private void initFrame()
   {
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
      setTitle(" DropDemo listing of "+ targetDir);
      setSize(300,400);
      setLocation(47,47);
      setVisible(true);
   }

   // -------------- vom DropTargetListener gefordete methoden -------------- \\

   public void dragEnter(DropTargetDragEvent e)
   { }

   public void dragExit(DropTargetEvent e)
   { }

   public void dragOver(DropTargetDragEvent e)
   { }

   public void drop(DropTargetDropEvent e)
   {
      try
      {
         Transferable tr = e.getTransferable();
         if (tr.isDataFlavorSupported (DataFlavor.javaFileListFlavor))
         {
            e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
            java.util.List files = (java.util.List)tr.getTransferData(DataFlavor.javaFileListFlavor);
            int len = files.size();
            File file;
            for(int i=0; i<len; i++)
            {
               file = (File)files.get(i);
               if( file.isFile() )
               {
                  try
                  {
                     copyFile(file, targetDir);
                     fileVector.add("<file> "+file.getName());
                     dropFileList.updateUI();
                  }
                  catch(IOException ex)
                  {
                  }
               }
            }
            e.getDropTargetContext().dropComplete(true);
         }
         else
         {
            System.err.println ("DataFlavor.javaFileListFlavor is not supported, rejected");
            e.rejectDrop();
         }
      }
      catch (IOException ex)
      {
         System.err.println ("IOException");
         ex.printStackTrace();
         e.rejectDrop();
      }
      catch (UnsupportedFlavorException ex)
      {
         System.err.println ("UnsupportedFlavorException");
         ex.printStackTrace();
         e.rejectDrop();
      }
   }

   public void dropActionChanged(DropTargetDragEvent e)
   { }

   private void copyFile(File srcFile, String targetDir)
      throws IOException
   {
      BufferedInputStream bis = new BufferedInputStream( new FileInputStream(srcFile) );
      int avail = bis.available();
      byte[] data = new byte[avail];
      bis.read(data);  // geht nur für kleine Dateien !
      bis.close();
      String target = targetDir + srcFile.getName() ;
      BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(target) );
      bos.write(data);
      bos.close();
   }

   public static void main(String[] args)
   {
      DropDemo wnd = new DropDemo();
   }
}
Valid XHTML 1.0 Strict top Back Next Up Home