Cara menyeret dan melepas dengan Java 2, Bagian 1

Jika Anda pernah memilih ikon file di browser sistem file seperti Windows Explorer dan menyeretnya ke ikon yang mewakili direktori lain (dan kemungkinan besar Anda memilikinya), Anda telah menggunakan seret dan lepas untuk mentransfer data. Jika Anda ingin menggunakan Java untuk mentransfer data, baca terus!

Java 2 (sebelumnya JDK 1.2) memperkenalkan kemampuan untuk mentransfer data menggunakan metafora drag and drop (D&D) yang sudah dikenal. Di Java 2, D & D menggunakan mekanisme transfer data dasar yang diperkenalkan di JDK 1.1 ( java.awt.datatransfer) untuk digunakan dengan papan klip. Meskipun artikel ini membahas operasi D&D dalam konteks komponen GUI, spesifikasinya tidak menyertakan batasan yang mencegah operasi program langsung.

Untuk mengembangkan metafora D&D, Java 2 mendefinisikan beberapa kelas baru dalam paket java.awt.dnd. Harap diperhatikan: Komponen GUI yang digunakan dalam artikel ini adalah komponen Swing. Pada kenyataannya, setiap subclass dari java.awt.Componentdapat digunakan.

Pertama, kita akan melihat bagaimana komponen GUI yang merepresentasikan sumber data operasi D&D memelihara keterkaitan dengan java.awt.dnd.DropSourceobjek.

Kedua, kita akan memeriksa bagaimana komponen GUI lain yang mewakili tujuan data operasi D & D mempertahankan keterkaitan dengan java.awt.dnd.DropTargetobjek.

Terakhir, kita akan menyelesaikan dengan java.awt.datatransfer.Transferableobjek yang merangkum data yang ditransfer antara objek DragSourcedan DropTarget.

Untuk mengunduh kode sumber dalam format zip atau tar, lihat Sumber.

DataFlavours dan tindakan

Saat Transferableobjek mengenkapsulasi data, itu membuat data tersedia DropTargetdalam berbagai format DataFlavors. Untuk transfer lokal dalam JVM yang sama (mesin virtual Java), Transferableberikan referensi objek.

Namun, untuk transfer ke JVM lain atau ke sistem asli, ini tidak masuk akal, jadi DataFlavormenggunakan java.io.InputStreamsubclass biasanya disediakan. (Meskipun diskusi tentang kelas transfer data berada di luar cakupan artikel ini, Anda akan menemukan daftar tautan artikel JavaWorld sebelumnya tentang topik ini di bagian Sumberdaya di bawah.)

Saat menjalankan operasi seret dan lepas, Anda dapat meminta berbagai tindakan seret dan lepas. The DnDConstantskelas mendefinisikan variabel kelas atas tindakan didukung:

  • ACTION_NONE - tidak ada tindakan yang diambil
  • ACTION_COPY - DragSourcemembiarkan data tetap utuh
  • ACTION_MOVE - DragSourcemenghapus data setelah berhasil menyelesaikan penurunan
  • ACTION_COPY atau ACTION_MOVE - DragSourceakan melakukan salah satu tindakan yang diminta olehDropTarget
  • ACTION_LINK atau ACTION_REFERENCE - perubahan data baik ke sumber atau tujuan menyebar ke lokasi lain

Membuat komponen yang dapat diseret

Agar komponen GUI berfungsi sebagai sumber operasi D & D, itu harus dikaitkan dengan lima objek:

  • java.awt.dnd.DragSource
  • java.awt.dnd.DragGestureRecognizer
  • java.awt.dnd.DragGestureListener
  • java.awt.datatransfer.Transferable
  • java.awt.dnd.DragSourceListener

DragSource

Cara umum untuk mendapatkan DragSourceobjek adalah dengan menggunakan satu instance per JVM. Metode kelas DragSource.getDefaultDragSourceakan mendapatkan DragSourceobjek bersama yang digunakan untuk seumur hidup JVM. Opsi lainnya adalah memberikan satu DragSourceper instance Componentkelas. Namun, dengan opsi ini, Anda menerima tanggung jawab untuk implementasi.

DragGestureRecognizer

Isyarat pengguna atau rangkaian isyarat yang memulai operasi D & D akan bervariasi per komponen, platform, dan perangkat:

Gerakan seret dan lepas Windows
Klik tombol kiri mouse Pindah
Kontrol, tombol kiri mouse Salinan
Shift-Control, tombol kiri mouse Tautan
Motif Gerakan drag and drop
Shift, BTransfer (tombol tengah) Pindah
Kontrol, BTransfer Salinan
Shift-Control, BTransfer Tautan

A DragGestureRecognizermerangkum detail implementasi ini, melindungi Anda dari dependensi platform. Metode instance dragSource.createDefaultDragGestureRecognizer()akan mendapatkan pengenal dan mengaitkannya dengan komponen, tindakan, dan DragGestureListener.

Contoh ini membuat subclass dari label Swing (JLabel). Dalam konstruktornya, kelas dan asosiasi yang diperlukan dibuat agar berfungsi sebagai sumber seret untuk operasi penyalinan atau pemindahan. Kami akan membahas pendengar selanjutnya. Inilah langkah pertama dalam membuat komponen yang bisa diseret:

kelas publik DragLabel extends JLabel {public DragLabel (String s) {this.setText (s); this.dragSource = DragSource.getDefaultDragSource (); this.dgListener = new DGListener (); this.dsListener = new DSListener ();

// komponen, tindakan, pendengar this.dragSource.createDefaultDragGestureRecognizer (ini, DnDConstants.ACTION_COPY_OR_MOVE, this.dgListener); } DragSource dragSource pribadi; pribadi DragGestureListener dgListener; pribadi DragSourceListener dsListener; }

DragGestureListener

Ketika DragGestureRecognizerterkait dengan komponen GUI mengenali tindakan D & D, itu pesan yang terdaftar DragGestureListener. Selanjutnya, DragGestureListenermengirimkan DragSourcesebuah startDragpesan yang mengatakan itu untuk memulai drag:

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

Ketika DragSourcemenerima startDragpesan, itu membuat DragSourceContextobjek konteks. Objek ini melacak status operasi dengan mendengarkan native DragSourceContextPeer. Dalam situasi ini, DragSourcedapat diperoleh dari Eventobjek atau variabel instan.

Hal khusus DragSourceListeneryang akan diinformasikan selama kemajuan operasi D & D ditetapkan sebagai parameter formal untuk dragGestureRecognized. Kursor seret awal yang menunjukkan status awal operasi D & D juga ditentukan sebagai parameter. Jika komponen yang dapat diseret tidak dapat menerima penurunan, kursor awal seharusnya DragSource.DefaultCopyNoDrop.

Jika platform Anda mengizinkannya, Anda dapat menentukan "gambar tarik" opsional untuk ditampilkan selain kursor. Platform Win32, bagaimanapun, tidak mendukung gambar seret.

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:

Kursor standar
DefaultCopyDrop DefaultCopyNoDrop
DefaultMoveDrop DefaultMoveNoDrop
DefaultLinkDrop DefaultLinkNoDrop

The DragSourceListenerobjek perubahan kursor dengan mengirimkan setCursor()pesan ke DragSourceContext- diperoleh dari DragSourceEventparameter. Selain itu, definisi metode dragOverdan dropActionChangedserupa. (Seperti yang akan kita lihat, metode ini tidak dipanggil jika DropTargetoperasi menolak.)

Inilah cara kami mengubah kursor untuk memberikan umpan balik seret:

 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.

Jika terjadi tindakan pemindahan, pemroses juga akan menghapus data sumber. (Jika itu sebuah komponen, itu akan dikeluarkan dari hierarki; jika itu adalah data teks yang ditampilkan dalam komponen teks, itu akan dihapus.)

Berikut ini adalah contoh dari dragDropEnd. Jika operasi tidak berhasil, metode akan kembali. Tindakan menjatuhkan diperiksa untuk melihat apakah itu adalah operasi pemindahan:

public void dragDropEnd (DragSourceDropEvent e) {if (e.getDropSuccess () == false) {return; } int dropAction = e.getDropAction (); if (dropAction == DnDConstants.ACTION_MOVE) // lakukan apa saja}

Tinjauan aliran

Mempertimbangkan kompleksitas pesan yang diteruskan di antara beberapa objek yang telah kita diskusikan, alangkah baiknya untuk meninjau alurnya: