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.Componentverwendet werden.

Zunächst wird untersucht, wie eine GUI-Komponente, die die Datenquelle einer D & D-Operation darstellt, eine Zuordnung zu einem java.awt.dnd.DropSourceObjekt 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.DropTargetObjekt aufrechterhält .

Schließlich werden wir mit einem java.awt.datatransfer.TransferableObjekt abschließen, das die zwischen den DragSourceund DropTargetObjekten übertragenen Daten kapselt .

Informationen zum Herunterladen des Quellcodes in den Formaten zip oder tar finden Sie unter Ressourcen.

DataFlavors und Aktionen

Wenn das TransferableObjekt Daten kapselt, stellt es die Daten auf DropTargetverschiedene Arten zur Verfügung DataFlavors. Stellt für eine lokale Übertragung innerhalb derselben JVM (Java Virtual Machine) Transferableeine Objektreferenz bereit.

Für Übertragungen an eine andere JVM oder an das native System ist dies jedoch nicht sinnvoll. Daher wird normalerweise die DataFlavorVerwendung einer java.io.InputStreamUnterklasse 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 DnDConstantsKlasse definiert die Klassenvariablen für die unterstützten Aktionen:

  • ACTION_NONE - keine Aktion ausgeführt
  • ACTION_COPY - das DragSourcelässt die Daten intakt
  • ACTION_MOVE - DragSourcelöscht die Daten nach erfolgreichem Abschluss des Abwurfs
  • ACTION_COPY oder ACTION_MOVE - DragSourcefü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 DragSourceObjekt zu erhalten, besteht darin, eine Instanz pro JVM zu verwenden. Die Klassenmethode DragSource.getDefaultDragSourceerhält ein gemeinsam genutztes DragSourceObjekt, das für die Lebensdauer der JVM verwendet wird. Eine andere Option besteht darin, eine DragSourcepro Instanz der ComponentKlasse 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-Gesten
Klicken Sie mit der linken Maustaste Bewegung
Steuerung, linke Maustaste Kopieren
Shift-Control, linke Maustaste Verknüpfung
Motiv Drag & Drop-Gesten
Shift, BTransfer (mittlere Taste) Bewegung
Kontrolle, BTransfer Kopieren
Schaltsteuerung, BTransfer Verknüpfung

A DragGestureRecognizerkapselt 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 DragGestureRecognizerder GUI-Komponente zugeordnete Komponente eine D & D-Aktion erkennt, wird eine Nachricht an die registrierte Person gesendet DragGestureListener. Als nächstes DragGestureListenersendet das DragSourceeine startDragNachricht, in der es aufgefordert wird, das Ziehen einzuleiten:

Schnittstelle DragGestureListener {public void dragGestureRecognized (DragGestureEvent e); }}

Wenn der DragSourcedie startDragNachricht empfängt , erstellt er ein DragSourceContextKontextobjekt. Dieses Objekt verfolgt den Status der Operation, indem es einem Muttersprachler zuhört DragSourceContextPeer. In dieser Situation DragSourcekann das vom EventObjekt 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:

Vordefinierte Cursor
DefaultCopyDrop DefaultCopyNoDrop
DefaultMoveDrop DefaultMoveNoDrop
DefaultLinkDrop DefaultLinkNoDrop

Das DragSourceListenerObjekt ändert den Cursor, indem es eine setCursor()Nachricht an den DragSourceContextvom DragSourceEventParameter erhaltenen - sendet . Darüber hinaus ist die Definition der Methoden dragOverund dropActionChangedähnlich. (Wie wir sehen werden, werden diese Methoden nicht aufgerufen, wenn DropTargetder 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: