Ziehen und Ablegen mit Java 2, Teil 1
Wenn Sie jemals ein Dateisymbol in einem Dateisystembrowser wie Windows Explorer ausgewählt und auf ein Symbol gezogen haben, das ein anderes Verzeichnis darstellt (und wahrscheinlich auch), haben Sie bereits Daten per Drag & Drop übertragen. Wenn Sie Java zum Übertragen von Daten verwenden möchten, lesen Sie weiter!
Java 2 (ehemals JDK 1.2) führte die Möglichkeit ein, Daten mithilfe der bekannten Drag & Drop-Metapher (D & D) zu übertragen. In Java 2 verwendet D & D den in JDK 1.1 ( java.awt.datatransfer
) eingeführten zugrunde liegenden Datenübertragungsmechanismus zur Verwendung mit der Zwischenablage. Obwohl dieser Artikel D & D-Operationen im Kontext von GUI-Komponenten beschreibt, enthält die Spezifikation keine Einschränkungen, die direkte programmatische Operationen verhindern.
Um die D & D-Metapher zu entwickeln, definiert Java 2 mehrere neue Klassen im Paket java.awt.dnd
. Bitte beachten Sie: Die in diesem Artikel verwendeten GUI-Komponenten sind Swing-Komponenten. Tatsächlich kann jede Unterklasse von java.awt.Component
verwendet werden.
Zunächst wird untersucht, wie eine GUI-Komponente, die die Datenquelle einer D & D-Operation darstellt, eine Zuordnung zu einem java.awt.dnd.DropSource
Objekt aufrechterhält .
Zweitens untersuchen wir, wie eine andere GUI-Komponente, die das Ziel der Daten einer D & D-Operation darstellt, eine Zuordnung zu einem java.awt.dnd.DropTarget
Objekt aufrechterhält .
Schließlich werden wir mit einem java.awt.datatransfer.Transferable
Objekt abschließen, das die zwischen den DragSource
und DropTarget
Objekten übertragenen Daten kapselt .
Informationen zum Herunterladen des Quellcodes in den Formaten zip oder tar finden Sie unter Ressourcen.
DataFlavors und Aktionen
Wenn das Transferable
Objekt Daten kapselt, stellt es die Daten auf DropTarget
verschiedene Arten zur Verfügung DataFlavors
. Stellt für eine lokale Übertragung innerhalb derselben JVM (Java Virtual Machine) Transferable
eine Objektreferenz bereit.
Für Übertragungen an eine andere JVM oder an das native System ist dies jedoch nicht sinnvoll. Daher wird normalerweise die DataFlavor
Verwendung einer java.io.InputStream
Unterklasse bereitgestellt. (Eine Diskussion der Datenübertragungsklassen würde den Rahmen dieses Artikels sprengen. Eine verknüpfte Liste früherer JavaWorld- Artikel zu diesem Thema finden Sie im Abschnitt Ressourcen unten.)
Wenn Sie eine Drag & Drop-Operation aufrufen, können Sie verschiedene Drag & Drop-Aktionen anfordern. Die DnDConstants
Klasse definiert die Klassenvariablen für die unterstützten Aktionen:
- ACTION_NONE - keine Aktion ausgeführt
- ACTION_COPY - das
DragSource
lässt die Daten intakt - ACTION_MOVE -
DragSource
löscht die Daten nach erfolgreichem Abschluss des Abwurfs - ACTION_COPY oder ACTION_MOVE -
DragSource
führt eine der von der angeforderten Aktionen ausDropTarget
- ACTION_LINK oder ACTION_REFERENCE - Eine Datenänderung an der Quelle oder am Ziel wird an den anderen Speicherort weitergegeben
Erstellen einer ziehbaren Komponente
Damit eine GUI-Komponente als Quelle für eine D & D-Operation fungiert, muss sie fünf Objekten zugeordnet sein:
- java.awt.dnd.DragSource
- java.awt.dnd.DragGestureRecognizer
- java.awt.dnd.DragGestureListener
- java.awt.datatransfer.Transferable
- java.awt.dnd.DragSourceListener
Die DragSource
Ein üblicher Weg, um ein DragSource
Objekt zu erhalten, besteht darin, eine Instanz pro JVM zu verwenden. Die Klassenmethode DragSource.getDefaultDragSource
erhält ein gemeinsam genutztes DragSource
Objekt, das für die Lebensdauer der JVM verwendet wird. Eine andere Option besteht darin, eine DragSource
pro Instanz der Component
Klasse bereitzustellen . Mit dieser Option übernehmen Sie jedoch die Verantwortung für die Implementierung.
Der DragGestureRecognizer
Die Benutzergeste oder der Satz von Gesten, die eine D & D-Operation initiieren, variiert je nach Komponente, Plattform und Gerät:
Windows Drag & Drop-GestenKlicken Sie mit der linken Maustaste | Bewegung |
Steuerung, linke Maustaste | Kopieren |
Shift-Control, linke Maustaste | Verknüpfung |
Shift, BTransfer (mittlere Taste) | Bewegung |
Kontrolle, BTransfer | Kopieren |
Schaltsteuerung, BTransfer | Verknüpfung |
A DragGestureRecognizer
kapselt diese Implementierungsdetails und schützt Sie so vor Plattformabhängigkeiten. Die Instanzmethode dragSource.createDefaultDragGestureRecognizer()
erhält einen Erkenner und ordnet ihn einer Komponente, Aktion und zu DragGestureListener
.
In diesem Beispiel wird eine Unterklasse eines Swing-Labels (JLabel) erstellt. In seinem Konstruktor werden die erforderlichen Klassen und Zuordnungen vorgenommen, damit er als Drag-Quelle für eine Kopier- oder Verschiebungsoperation fungiert. Wir werden als nächstes die Zuhörer besprechen. Hier ist der erste Schritt zum Erstellen einer ziehbaren Komponente:
öffentliche Klasse DragLabel erweitert JLabel {public DragLabel (String s) {this.setText (s); this.dragSource = DragSource.getDefaultDragSource (); this.dgListener = neuer DGListener (); this.dsListener = neuer DSListener ();
// Komponente, Aktion, Listener this.dragSource.createDefaultDragGestureRecognizer (this, DnDConstants.ACTION_COPY_OR_MOVE, this.dgListener); } private DragSource dragSource; privater DragGestureListener dgListener; privater DragSourceListener dsListener; }}
Der DragGestureListener
Wenn die DragGestureRecognizer
der GUI-Komponente zugeordnete Komponente eine D & D-Aktion erkennt, wird eine Nachricht an die registrierte Person gesendet DragGestureListener
. Als nächstes DragGestureListener
sendet das DragSource
eine startDrag
Nachricht, in der es aufgefordert wird, das Ziehen einzuleiten:
Schnittstelle DragGestureListener {public void dragGestureRecognized (DragGestureEvent e); }}
Wenn der DragSource
die startDrag
Nachricht empfängt , erstellt er ein DragSourceContext
Kontextobjekt. Dieses Objekt verfolgt den Status der Operation, indem es einem Muttersprachler zuhört DragSourceContextPeer
. In dieser Situation DragSource
kann das vom Event
Objekt oder von einer Instanzvariablen erhalten werden.
Die Person DragSourceListener
, die während des Fortschritts der D & D-Operation informiert wird, wird als formaler Parameter für angegeben dragGestureRecognized
. Der anfängliche Ziehcursor, der den vorläufigen Status der D & D-Operation anzeigt, wird ebenfalls als Parameter angegeben. Wenn die ziehbare Komponente keine Tropfen akzeptieren kann, sollte der ursprüngliche Cursor sein DragSource.DefaultCopyNoDrop
.
Wenn Ihre Plattform dies zulässt, können Sie ein optionales "Drag Image" angeben, das zusätzlich zu den Cursorn angezeigt werden soll. Win32-Plattformen unterstützen jedoch keine Drag-Images.
A Transferable
object encapsulates the data -- most likely associated with the Component
(that is, the label's text) -- that will be transferred. Here's how to start a drag:
public void dragGestureRecognized(DragGestureEvent e) { // check to see if action is OK ... try { Transferable transferable = ... //initial cursor, transferable, dsource listener e.startDrag(DragSource.DefaultCopyNoDrop, transferable, dsListener); // or if dragSource is an instance variable: // dragSource.startDrag(e, DragSource.DefaultCopyNoDrop, transferable, dsListener); }catch( InvalidDnDOperationException idoe ) { System.err.println( idoe ); } }
The Transferable object
The java.awt.datatransfer.StringSelection
class works well for transfers within the same JVM but suffers from a ClassCastException
when used in inter-JVM cases. To solve this problem, you'll have to provide a custom Transferable
object.
The custom Transferable
object creates instances of the DataFlavors
it wishes to provide. The Transferable
interface directs method getTransferDataFlavors()
to return an array of these flavors. To this end, we create a java.util.List
representation of this array to facilitate the implementation of isDataFlavorSupported(DataFlavor)
.
This example provides two flavors. Since we're simply transferring text data, we can use the two predefined DataFlavor
flavors. For local transfers (within the same JVM), we can use DataFlavor.stringFlavor
. For nonlocal transfers, we prefer DataFlavor.plainTextFlavor
, since its internal representation class is a java.io.InputStream
.
Moreover, we could define our own DataFlavors
to map to MIME types such as image/JPEG, or define custom-text charsets such as Latin-1; but we'll save that discussion for a future article.
Although the Transferable
doesn't necessarily have to be a ClipboardOwner
for drag and drop, enabling this functionality will make it available for clipboard transfers.
Let's see the definition of a simple Transferable
for text data:
public class StringTransferable implements Transferable, ClipboardOwner { public static final DataFlavor plainTextFlavor = DataFlavor.plainTextFlavor; public static final DataFlavor localStringFlavor = DataFlavor.stringFlavor;
public static final DataFlavor[] flavors = { StringTransferable.plainTextFlavor, StringTransferable.localStringFlavor };
private static final List flavorList = Arrays.asList( flavors );
public synchronized DataFlavor[] getTransferDataFlavors() { return flavors; } public boolean isDataFlavorSupported( DataFlavor flavor ) { return (flavorList.contains(flavor)); }
The Transferable
provides the data for the flavors it supports via its getTransferData
method. However, if an unsupported flavor is requested, an exception will be thrown. If a local (same JVM) transfer is requested via the StringTransferable.localStringFlavor
, an object reference is returned. Note: Object references don't make sense outside of the JVM.
A subclass of java.io.InputStream
should be provided for native-to-Java or inter-JVM requests.
For StringTransferable.plainTextFlavor
requests, getTransferData
returns a java.io.ByteArrayInputStream
. Text data may have different character encodings as specified in the MIME specification. (For more on the MIME specification, see Resources.)
The DataFlavor
should be queried for the encoding requested by the DropTarget
. Common character encodings are Unicode and Latin-1 (ISO 8859-1).
Here's how the Transferable
can provide text data in a variety of formats and encodings:
public synchronized Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (flavor.equals(StringTransferable.plainTextFlavor)) { String charset = flavor.getParameter("charset").trim(); if(charset.equalsIgnoreCase("unicode")) { System.out.println("returning unicode charset"); // uppercase U in Unicode here! return new ByteArrayInputStream(this.string.getBytes("Unicode")); } else { System.out.println("returning latin-1 charset"); return new ByteArrayInputStream(this.string.getBytes("iso8859-1")); } } else if (StringTransferable.localStringFlavor.equals(flavor)) { return this.string; } else { throw new UnsupportedFlavorException (flavor); } }
The DragSourceListener
The DragSourceListener
is responsible for providing "drag over" effects during the D&D operation. Drag over effects provide visual feedback while the cursor is over a component, but do not permanently change the appearance of components.
interface DragSourceListener { public void dragEnter(DragSourceDragEvent e); public void dragOver(DragSourceDragEvent e); public void dragExit(DragSourceEvent e); public void dragDropEnd(DragSourceDropEvent e); public void dropActionChanged (DragSourceDragEvent e); }
Usually the DragSourceListener
accomplishes drag over effects via cursor changes. There are two possible cursors:
- A Drop cursor, which is displayed while over a valid active-DropTarget
- A NoDrop cursor, which is displayed while over anything else
The DragSource
class has several predefined cursors as class variables:
DefaultCopyDrop | DefaultCopyNoDrop |
DefaultMoveDrop | DefaultMoveNoDrop |
DefaultLinkDrop | DefaultLinkNoDrop |
Das DragSourceListener
Objekt ändert den Cursor, indem es eine setCursor()
Nachricht an den DragSourceContext
vom DragSourceEvent
Parameter erhaltenen - sendet . Darüber hinaus ist die Definition der Methoden dragOver
und dropActionChanged
ähnlich. (Wie wir sehen werden, werden diese Methoden nicht aufgerufen, wenn DropTarget
der Vorgang abgelehnt wird.)
So können wir den Cursor ändern, um Feedback über Drag & Drop zu erhalten:
public void dragEnter(DragSourceDragEvent e) { DragSourceContext context = e.getDragSourceContext(); //intersection of the users selected action, and the source and target actions int myaction = e.getDropAction(); if( (myaction & DnDConstants.ACTION_COPY) != 0) { context.setCursor(DragSource.DefaultCopyDrop); } else { context.setCursor(DragSource.DefaultCopyNoDrop); } }
When the operation has ended, the DragSourceListener
receives notification from a dragDropEnd
message. When so notified, the listener's responsibility is to check the success of the operation, then, if successful, perform the requested action. If the operation isn't successful there's nothing for the DragSourceListener
to do.
Im Falle einer Verschiebungsaktion entfernt der Listener auch die Quelldaten. (Wenn es sich um eine Komponente handelt, wird sie aus der Hierarchie entfernt. Wenn es sich um die in einer Textkomponente angezeigten Textdaten handelt, werden sie gelöscht.)
Das Folgende ist ein Beispiel für dragDropEnd
. Wenn die Operation nicht erfolgreich ist, geben die Methoden einfach zurück. Die Abwurfaktion wird überprüft, um festzustellen, ob es sich um eine Verschiebungsoperation handelt:
public void dragDropEnd (DragSourceDropEvent e) {if (e.getDropSuccess () == false) {return; } int dropAction = e.getDropAction (); if (dropAction == DnDConstants.ACTION_MOVE) // mach was auch immer}
Flussüberprüfung
Angesichts der Komplexität der Nachrichten, die zwischen den verschiedenen von uns diskutierten Objekten übertragen werden, ist es gut, den Ablauf zu überprüfen: