Advanced Java Services | Clipboardhandling |
Mit Hilfe der Klasse Clipboard kann eine JavaApplikation sich eine eigenes Clipboard einrichten oder auch das Clipboard des Betriebssystems benützen. Die Vorgehensweise ist dabei, so wie es der Philosophie von Java entspricht, unabhängig vom jeweiligen Betriebssystem. Hat man ein Clipboardobjekt instanziiert, so kann man mit der Methode getContents() auf den Inhalt zugreifen und mit der Methode setContents() Daten in das Clipboard übertragen. Mit der Version 1.5 bzw. 5 steht mit der Methode getData(DataFlavor flavor) eine zweite Methode zur Verfügung, mit der man Daten aus dem Clipboard direkt abholen kann, ohne den Weg über ein Transferableobjekt gehen zu müssen. Es folgt ein Auszug aus der API.
Attribute | |
Typ | Name des Attributs |
protected Transferable | contents Enthält die Daten des Clipboards. |
protected ClipboardOwner | owner Der Eigentümer des Clipboards. |
Konstruktor | |
Clipboard(String name) | Creates a clipboard object. |
Wichtige Methoden | |
Returntyp | Name der Methode |
void | addFlavorListener(FlavorListener listener) Registers the specified FlavorListener to receive FlavorEvents from this clipboard. Since 1.5 |
void | removeFlavorListener(FlavorListener listener) Removes the specified FlavorListener so that it no longer receives FlavorEvents from this Clipboard. Since 1.5 |
DataFlavor[] | getAvailableDataFlavors() Returns an array of DataFlavors in which the current contents of this clipboard can be provided. Since 1.5 |
Transferable | getContents(Object requestor) Returns a transferable object representing the current contents of the clipboard. If the clipboard currently has no contents, it returns null. The parameter Object requestor is not currently used. |
String | getName() Returns the name of this clipboard object. |
boolean | isDataFlavorAvailable(DataFlavor flavor) Returns whether or not the current contents of this clipboard can be provided in the specified Since 1.5 |
void | setContents(Transferable contents, ClipboardOwner owner) Sets the current contents of the clipboard to the specified transferable object and registers the specified clipboard owner as the owner of the new contents. |
Mit dem oben angeführten Konstruktor kann sich eine JavaApplikation ein eigenes Clipboard einrichten. Oft ist man jedoch auch daran interessiert, auf das systemeigene Clipboard zuzugreifen, um Daten aus anderen Anwendungen in eine JavaApplikation zu holen oder umgekehrt. Der Zugriff auf das Systemclipboard erfolgt über die wichtige Klasse Toolkit.
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); System.out.println("Clipboard type : " + cb.getClass().getName() ); // Ausgabe : sun.awt.windows.WClipboard (auf Windowssystemen) System.out.println("Clipboard name : " + cb.getName() ); // Ausgabe : System
Egal, mit welchem Clipboard man arbeitet, die Vorgehensweise ist immer die gleiche.
Der Zugriff auf den Inhalt das Clipboards erfolgt über die Schnittstelle Transferable. Die im Clipboard zwischengespeicherten Daten werden mit Hilfe der Methode getContents() geliefert. Diese Methode liefert die Daten in der Form eines eigenen Typs mit dem Namen Transferable. Wenn man weiß, welcher Typ von Daten anliegt, dann kann man die Daten aus dem Transferableobjekt abholen und auf einen Javatyp casten.
Will man umgekehrt Daten in das Clipboard bringen, muß man die Daten in ein Transferableobjekt übertragen, da ein Clipboard nur mit einem Transferableobjekt kommunizieren kann. Für Textdaten bietet Java dazu die Klasse StringSelection an. Für andere Datenformen gibt es keine entsprechenden Klassen. In diesen Fällen muß man sich eigene klassen schreiben, die das Interface Transferable implementieren. Wir werden dies hier zeigen und sowohl für Imagedaten als auch für Dateien eigene Klassen entwickeln. Weiter unten werden solche Klassen entwickelt. Mit dieser Klasse TransferableImage gelingt es, Bilddaten in das Systemclipboard (oder auch ein eigenes) zu bringen. Mit der Klasse TransferableFile wird es uns gelingen Dateien über das Clipboard zu verschieben. Zunächst stellen wir das Interface Transferable vor.
Wichtige Methoden | |
Returntyp | Name der Methode |
Object | getTransferData(DataFlavor flavor) Returns an object which represents the data to be transferred. throws UnsupportedFlavorException, IOException |
DataFlavor[] | getTransferDataFlavors() Returns an array of DataFlavor objects indicating the flavors the data can be provided in. |
boolean | isDataFlavorSupported(DataFlavor flavor) Returns whether or not the specified data flavor is supported for this object. |
Ein Objekt vom Typ Transferable hält die Daten aus dem Clipboard. getTransferData() liefert dann diese Daten, aber nur, wenn der richtige Datentyp in Form eines DataFlavorobjekts übergeben wird. Ansonsten wirft die Methode eine UnsupportedFlavorException.
Konstanten | |
Typ | Name der Konstante |
static DataFlavor | imageFlavor The DataFlavor representing a Java Image class (representationClass = java.awt.Image. mimeType = "image/x-java-image"). |
static DataFlavor | javaFileListFlavor To transfer a list of files to/from Java (and the underlying platform) a DataFlavor of this type/subtype and representation class of java.util.List is used. |
static DataFlavor | stringFlavor The DataFlavor representing a Java Unicode String class (representationClass = java.lang.String, mimeType = "application/x-java-serialized-object") |
Konstruktoren | |
DataFlavor(Class<?> representationClass, String humanPresentableName) | Constructs a DataFlavor that represents a Java class. |
DataFlavor(String mimeType) | Constructs a DataFlavor from a mimeType string. |
DataFlavor(String mimeType, String humanPresentableName) | Constructs a DataFlavor that represents a MimeType. |
In unseren Beispielen werden wir nur Konstanten vom Typ DataFlavor verwenden. Aber auch für selbst kreierte Objekte ist das Vorgehen nicht anders, denn : DataFlavor objects are constant and never change once instantiated. (API)
Als letzte Klasse der API stellen wir noch Stringselection vor. StringSelection implementiert das Interface Transferable. Wir werden diese Klasse brauchen, um Strings in die Zwischenablage zu transportieren.
Konstruktoren | |
StringSelection(String data) | Creates a Transferable capable of transferring the specified String. |
Methoden | |
Returntyp | Name der Methode |
Object | getTransferData(DataFlavor flavor) Returns the Transferable's data in the requested DataFlavor if possible. |
DataFlavor[] | getTransferDataFlavors() Returns an array of flavors in which this Transferable can provide the data. |
boolean | isDataFlavorSupported(DataFlavor flavor) Returns whether the requested flavor is supported by this Transferable. |
void | lostOwnership(Clipboard clipboard, Transferable contents) Notifies this object that it is no longer the clipboard owner. |
Im folgenden Teil werden wir den Copy/Paste-mechansimus für die Datentypen String, Image und File erläutern. Alle Beispiele sollen für eigene Clipboards und das Systemclipboard brauchbar sein. Am einfachsten ist Vorgehen für Strings, da hier die Klasse StringSelection zur Verfügung steht. Im Falle des Datentyps Image wird es möglich sein beliebige ImageObjekte in das Systemclipboard zu bringen bzw. abzuholen. Dazu entwickeln wir die Klasse TransferableImage. Im letzten Beispiel wollen wir FileObjekte in einer JList speichern. Dabei sollen die Fileobjekte in der Liste reale Dateien auf einem externen Speichermedium darstellen. JList soll so gestaltet werden, daß der Datenaustausch sowhl von einer JList zur anderen als auch zum Betriebssystem funktioniert.
Wir beginnen jedesmal mit Paste, da das Abholen der Daten aus einem Clipboard generell der einfachere Vorgang ist.
Da es einfacher ist, Daten aus dem Clipboard herauszuholen, fangen wir damit an.
Die Daten des Clipboards befinden sich im Attribut contents. Die Methode getContents(Object requestor) liefert die Daten zunächst in der Form eines Types Transferable. Transferable ist ein InterfaceTyp. Die Methode liefert also eine Referenz auf ein Objekt, dessen Klasse das Interface implementiert hat. Der Parameter requestor wird zur Zeit noch nicht verwendet.
Transferable trans = cb.getContents(null); // Parameter wird nicht benützt System.out.println("Transferable type : " + trans.getClass().getName() ); // Ausgabe : sun.awt.datatransfer.ClipboardTransferable
Liegen keine Daten an, so liefert getContents() null. Das wird eher selten der Fall sein. Irgendetwas liegt meistens in der Zwischenablage. Mit der Methode isDataFlavorSupported(DataFlavor flavor) von Transferable erfährt man, welche Art von Daten anliegen. Die Daten werden also nach verschiedenen "Geschmacksrichtungen" unterschieden. Dafür gibt es die Klasse Dataflavor. Sie hält statische Konstanten bereit, mit denen man die Daten im Transferableobjekt untersuchen kann.
System.out.println("StringFlavor is supported : " + trans.isDataFlavorSupported(DataFlavor.stringFlavor)); // true, falls ein String anliegt, false sonst
Die Methode getTransferData(DataFlavor flavor) liefert die Daten in einer lesbaren Form, falls der flavor zu den Daten passt. Wenn nicht, so wird eine Exception mit dem schönen Namen UnsupportedFlavorException geworfen.
try { Object data = trans.getTransferData(DataFlavor.stringFlavor) ; // data kann nun auf String heruntergecastet werden } catch(UnsupportedFlavorException ex) { System.out.println("Daten enthalten keinen Text"); } catch(IOException ex) { System.out.println(ex); }
textarea.replaceRange( (String)data, tear.getSelectionStart(), tear.getSelectionEnd()); //Falls kein Text markiert ist, wird an der aktuellen Cursorposition eingefügt
Will man Daten aus der eigenen Anwendung exportieren, muß man genau den umgekehrten Weg gehen. Also muß man es schaffen, einen String in ein Transferableobjekt zu verwandeln. Für Strings ist das keine Arbeit, da es die Klasse StringSelection gibt. StringSelection implementiert das Interface Transferable.
Der folgende Codeausschnitt holt den markierten Text aus eine (J)TextArea und legt damit ein Objekt vom Typ StringSelection an.
String selected = tear.getSelectedText(); if (selected!=null && selected.length()!=0) { StringSelection clipText = new StringSelection(selected); }
Clipboard bietet dazu die Methode setContents(Transferable contents, ClipboardOwner owner) an. Da die Klasse StringSelection auch das Interface ClipboardOwner implementiert, kann man für beide Parameter das soeben erzeugte StringSelectionobjekt hernehmen.
cb.setContents(clipText, clipText);
Will man cut statt copy realisieren muß man noch den markierten Text, etwa in einer (J)TextArea löschen:
textarea.replaceRange("", tear.getSelectionStart(), tear.getSelectionEnd());
Das war's.
Seit der Version 1.4 ist es ziemlich einfach, auch Bilder aus der Zwischenablage zu holen. Die systemabhängige Implementierung liefert die Bilder in einem systemunabhängigen Javaformat. Entlockt man dem mit getTransferData(DataFlavor.imageFlavor) erhaltenem Objekt mit getClass().getName() seinen eigentlichen Typ, so erhält man den Typ BufferedImage. BufferedImage ist der komfortabelste Imagetyp, den Java zu bieten hat. Ein BufferedImage kann man überall einsetzen, wo man ein Image braucht. Man kann es daher problemlos auch in ein ImageIcon umwandeln. Zudem ist es in verschiedenen Formaten auf Platte speicherbar.
Die Konstante DataFlavor.imageFlavor ist eine der nützlichen Errungenschaften der Version 1.4. Ab dieser Version kann man Bilder ohne großen Aufwand aus der Zwischenablage holen, bzw. sie auch dort ablegen.
System.out.println("ImageFlavor is supported : " + trans.isDataFlavorSupported(DataFlavor.imageFlavor)); // true, falls ein Image anliegt, false sonst
Hat man sich davon überzeugt, daß Bilddaten anliegen, kann man sie genauso abholen wie einen String.
try { Object data = trans.getTransferData(DataFlavor.imageFlavor) ; // data kann nun auf BufferedImage heruntergecastet werden } catch(UnsupportedFlavorException ex) { System.out.println("Daten enthalten kein Image"); } catch(IOException ex) { System.out.println(ex); }
BufferedImage img = (BufferedImage)data; imageLabel.setIcon( new ImageIcon(img) );
Um Bilder in das Clipboard zu bringen muß man sich etwas mehr anstrengen. Es gibt nämlich zu StringSelection kein Äquivalent für Images. Also schreiben wir es selbst. Das ist nach einiger Überlegung garnicht so schwer. Sun's Implementierung sun.awt.datatransfer.ClipboardTransferable des Interfaces Transferable liefert beim Aufruf der Methode getTransferData(DataFlavor.imageFlavor) im Erfolgsfall ein Bild vom Typ BufferedImage. Wir legen uns also eine Klasse an, die ein BufferedImage als Attribut enthält und dieses mit der Methode getTransferData(DataFlavor flavor) herausrückt. Die Konstruktoren haben die Aufgabe, dieses BufferedImage aus anderen Imagetypen zu erzeugen. Ich habe die Klasse TransferableImage genannt.
public class TransferableImage implements Transferable { private BufferedImage bufImg; public TransferableImage(ImageIcon ic) { this( ic.getImage() ); } public TransferableImage(Image img) { int w = img.getWidth(null); // es muß keinen ImageObserver geben int h = img.getHeight(null); bufImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); // einfachster Fall : BufferedImage.TYPE_INT_RGB // mit drawImage das Image auf ein BufferedImage zeichnen Graphics g = bufImg.createGraphics(); g.drawImage(img, 0, 0, null); g.dispose(); } public TransferableImage(BufferedImage bImg) { bufImg = bImg; } //Returns an object which represents the data to be transferred. public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if( flavor.equals(DataFlavor.imageFlavor) ) return bufImg; 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.imageFlavor} ; } //Returns whether or not the specified data flavor is supported //for this object. public boolean isDataFlavorSupported(DataFlavor flavor) { return flavor.equals(DataFlavor.imageFlavor) ; } }
Die Klasse verfügt über drei Konstruktoren, die alle Ansprüche abdecken sollten. Man kann ein ImageIcon, ein Image oder ein BufferedImage übergeben. Zur Funktionsweise der Konstruktoren verweise ich auf Image / Icon / BufferedImage / Screenshot und die Abschnitte "Vom Image zum ImageIcon und umgekehrt" und "Ein Image in ein BufferedImage verwandeln".
Die Methode getTransferData() liefert das BufferedImage, falls die Konstante DataFlavor.imageFlavor übergeben wird.
Die Methode getTransferDataFlavors() liefert ein Array von flavors der Länge 1, das die Konstante DataFlavor.imageFlavor enthält. Die Klasse ist eben nur für diesen flavor zuständig.
Auch die Implementierung der dritten Methode ist offensichtlich.
Um analog wie bei Strings die Methode setContents(Transferable contents, ClipboardOwner owner) anwenden zu können brauchen wir noch einen ClipboardOwner. Ja, aber nur formal. Ein Blick in den Quellcode von Clipboard zeigt, daß man problemlos als owner null setzen kann.
public synchronized void setContents(Transferable contents, ClipboardOwner owner) { final ClipboardOwner oldOwner = this.owner; final Transferable oldContents = this.contents; this.owner = owner; this.contents = contents; if (oldOwner != null && oldOwner != owner) { oldOwner.lostOwnership(this, oldContents); } }
Ist hier owner null, so wird die Methode lostOwnership() nicht gerufen. Schaut man sich zudem die Implementierung des Interfaces ClipboardOwner von Stringselection an, so sieht man, daß die Implementierung leer ist.
public void lostOwnership(Clipboard clipboard, Transferable contents) { }
Um für zukünftige Erweiterungen gerüstet zu sein, verfahren wir ebenso und implementieren in der Klasse TransferableImage auch das Interface ClipboardOwner :
public class TransferableImage implements Transferable, ClipboardOwner
{
private BufferedImage bufImg;
public TransferableImage(ImageIcon ic)
{
this( ic.getImage() );
}
public TransferableImage(Image img)
{
int w = img.getWidth(null); // es muß keinen ImageObserver geben
int h = img.getHeight(null);
bufImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
// einfachster Fall : BufferedImage.TYPE_INT_RGB
// mit drawImage das Image auf ein BufferedImage zeichnen
Graphics g = bufImg.createGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
}
public TransferableImage(BufferedImage bImg)
{
bufImg = bImg;
}
//Returns an object which represents the data to be transferred.
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException
{
if( flavor.equals(DataFlavor.imageFlavor) )
return bufImg;
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.imageFlavor} ;
}
//Returns whether or not the specified data flavor is supported
//for this object.
public boolean isDataFlavorSupported(DataFlavor flavor)
{
return flavor.equals(DataFlavor.imageFlavor) ;
}
// Implementierung des Interfaces ClipboardOwner
public void lostOwnership(Clipboard clipboard, Transferable contents)
{
}
}
Der folgende Codeausschnitt holt ein Bild aus einem JLabel, verwandelt es in ein TransferableImage und schiebt dieses in die Zwischenablage.
Icon icon = imageLabel.getIcon() ; if ( icon instanceof ImageIcon ) { TransferableImage ti = new TransferableImage( (ImageIcon)icon ); clipboard.setContents(ti, ti); }
Nun kann man mit irgendeiner Bildbearbeitungssoftware (z.B. PhotoEditor) das Bild aus der Zwischenablage holen und einfügen.
Wir skizzieren zunächst die Konfiguration einer JList, die Dateien anzeigen soll.
DefaultListModel defaultListModel = new DefaultListModel(); JList fileList = new JList(defaultListModel); fileList.addKeyListener( new PasteHandler() ); fileList.setCellRenderer( new FileListCellRenderer() ); JScrollPane sp = new JScrollPane(fileList);
Wir verwenden ein DefaultListModel. Es arbeitet mit einem Vector und kann beliebige Objekte aufnehmen.
Wir werden File-Objekte aufnehmen. Mit CTRL+V wollen wir Dateien aus dem Clipboard holen. Dazu brauchen
wir einen KeyListener. natürlich wollen wir, daß mit Copy/Paste wiklich Dateien kopiert werden und nicht
nur die Namen der Dateien.
Da der DefaultRenderer entweder Text oder Icons anzeigen kann, aber nicht beides,
wollen wir einen eigenen Renderer verwenden. Dies ist ein schönes GUI-feature, aber für Copy/Paste
unerheblich.
File root = new File("c:/"); // oder new File("/") File[] contents = root.listFiles() ; for(int i=0; i<contents.length; i++) defaultListModel.addElement(contents[i]) ;
Wir nehmen nun an, daß im Clipboard eine Datei abgelegt worden ist (z.Bsp. dadurch, daß in einem Arbeitsplatzfenster eine Datei mit CTRL-C ins Clipboard befördert wurde) und wollen diese Datei sowohl in die JListe aufnehmen als auch physikalisch kopieren.
class PasteHandler extends KeyAdapter
{
public void keyPressed(KeyEvent e)
{
int kCode = e.getKeyCode();
int mod = e.getModifiers();
if(kCode == KeyEvent.VK_V && mod == KeyEvent.CTRL_MASK)
{
//System.out.println("control+v"); // ok
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
paste(cb);
}
}
}
Wir überprüfen in keyPressed() die Tastenkombination, holen uns das SystemClipboard (oder ein anderes) und delegieren die Aufgabe an die Methode paste():
private void paste(Clipboard cb)
{
Transferable tr = cb.getContents(null); // Parameter wird nicht benützt
try
{
java.util.List files = (java.util.List)tr.getTransferData(DataFlavor.javaFileListFlavor);
int len = files.size();
File targetDir = getTargetDir();
File file;
for(int i=0; i<len; i++)
{
file = (File)files.get(i);
copyFiles(file, targetDir); // wirft IOException
}
updateList(targetDir);
}
catch(UnsupportedFlavorException ex)
{
System.out.println ("DataFlavor.javaFileListFlavor is not supported, rejected");
}
catch(IOException ex)
{
System.out.println ("IOException " +ex);
//ex.printStackTrace();
}
}
Wesentlich ist die erste Zeile. Wir übergeben der Methode getTransferData() die Konstante DataFlavor.javaFileListFlavor. Liegen Dateien im Clipboard, so liefert diese Methode eine Liste von File-Objekten, andernfalls wird eine UnsupportedFlavorException geworfen. Hat man die Quelldateinamen und das Zielverzeichnis, so kann man den Kopiervorgang an die Methode copyFiles() delegieren. Diese wird hier nicht implementiert. Danach müssen die kopierten Dateien noch in die Liste aufgenommen werden. Dies ist wiederum nur eine Skizze. Eine saubere Implementierung muß selbstverständlich auch jeden einzelnen Kopiervorgang überprüfen.
Um Dateien ins Clipboard zu transferieren gehen wir ähnlich wie im letzten Abschnitt vor und schreiben uns eine Klasse TransferableFile. Aus der Beschreibung der Konstanten DataFlavor.javaFileListFlavor in der API erkennt man, daß man dem Clipboard die Daten in Form einer Liste von Fileobjekten übergeben muß. Genau diesem Zweck dient die Klasse TransferableFile
import java.io.*;
import java.util.*;
import java.awt.datatransfer.*;
public class TransferableFile implements Transferable
{
private Vector fileList;
public TransferableFile(String fileName)
{
fileList = new Vector();
fileList.add( new File(fileName) ) ;
}
public TransferableFile(File file)
{
fileList = new Vector();
fileList.add( file ) ;
}
public TransferableFile(String[] fileNames)
{
fileList = new Vector();
for(int i=0; i<fileNames.length; i++)
fileList.add( new File(fileNames[i]) ) ;
}
public TransferableFile(File[] files)
{
fileList = new Vector();
for(int i=0; i<files.length; i++)
fileList.add( files[i] ) ;
}
public TransferableFile(Vector files)
{
// we had to make a copy, because the elements of files can change !
fileList = new Vector(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) ;
}
}
Die Konstruktorausstattung ist üppig und sollte alle Fälle abdecken. Zentral ist die Methode getTransferData(), über die das Clipboard die Liste erhält.
Wie bei Images könnten wir wieder mit clipboard.setContents() arbeiten. Im KeyListener lauscht man dazu auf die Tastenkombination CTRL+C oder CTRL+X und setzt dann clipboard.setContents() ab.
Interessanter ist der Weg über einen TransferHandler, der in der Version 1.4 für Swingkomponenten eingeführt wurde. TransferHandler vereinfachen den Drag-teil von Drag und Drop und exportieren nebenbei die Daten auch noch ins Clipboard (Drag and Drop wird ausführlich im nächsten Kapitel behandelt). JList hat einen TransferHandler vorinstalliert, der allerdings nur für Strings geeignet ist. Für unsere Bedürfnissse brauchen wir einen eigenen TransferHandler. Der Vorteil dieser Variante ist, daß wir hier den Datenexport in das Clipboard nicht über einen Listener überwachen müssen, weil er automatisch initiert wird. Zudem hat man mit dieser Variante auch schon die erste Hälfte von Drag und Drop codiert.
Das folgende ist zu tun:
import java.io.*;
import java.util.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
public class ListTransferHandler extends TransferHandler
{
private Vector files ;
public ListTransferHandler()
{
files = new Vector();
}
public ListTransferHandler(String text)
{
super(text);
files = new Vector();
}
/**
* Create a Transferable to use as the source for a data transfer.
* @param c The component holding the data to be transfered.
* @return The representation of the data to be transfered.
*/
public Transferable createTransferable(JComponent c)
{
if (c instanceof JList)
{
JList list = (JList) c;
Object[] values = list.getSelectedValues();
if (values == null || values.length == 0)
{
return null;
}
for(int i=0; i<values.length; i++)
{
files.add( values[i] );
}
return new TransferableFile(files);
}
else
return null;
}
public int getSourceActions(JComponent c)
{
System.out.println("getSourceActions");
return COPY; // or return COPY_OR_MOVE;
}
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)
{
super.exportDone(source, data, action) ;
files.removeAllElements(); // empty the vector for the next operation
}
} // end class ListTransferHandler
Wie man sieht, arbeitet unser ListTransferHandler mit der Klasse TransferableFile zusammen. Die Methoden exportToClipboard() und exportAsDrag() brauchen nicht (und sollen in der Regel auch nicht) überschrieben werden. Mit Hilfe der Meldungen kann man jedoch die automatische Initiierung erkennen. In der Methode exportDone() kann man Aufräumungsarbeiten erledigen.
Dazu braucht man zwei Zeilen.
fileList.setDragEnabled(true); fileList.setTransferHandler( new ListTransferHandler() );
Im nächsten Kapitel über Drag und Drop werden wir die Klassen ListTransferHandler und TransferableFile noch einmal brauchen.
Zum Schluß noch eine kleine Verzierung. Es wäre schön, wenn unsere JList Icons und Text anzeigen könnte. Dieses Problem wird durch einen eigenen Renderer gelöst im Kapitel über JList im Abschnitt "Listen mit Text und Bildern (Icons)". Ein Beispiel für einen passenden Renderer steht dort im Quelltext. Hier zeigen wir, wie man über die Klasse JFileChooser an die Icons herankommt. Die folgende Skizze zeigt, wie man das zu einer Datei passende Icon erhält.
JFileChooser choo = new JFileChooser(); FileChooserUI chooUI = choo.getUI(); FileView fileView = chooUI.getFileView(choo); Icon icon = fileView.getIcon(file) ;
Und so sehen die Icons aus (Java Version 1.5, W2K)
Wer schon mit dem JFileChooser gearbeitet hat, der kennt sie.