拖放(Drag and Drop,DnD)是現代 GUI 應用不可或缺的交互方式。Java 在 AWT 階段即提供完整的 DnD 框架(java.awt.dnd),Swing 組件則進一步封裝簡化了常見操作。本文將系統講解 DropTarget(放置目標)、DragSource(拖曳源) 兩大核心類,並展示如何啟用 Swing 文字組件的內建拖放支援。
DnD 操作涉及三大角色:
Transferable 資料物件DropTargetListener 處理放置邏輯DataFlavor(資料格式)流程:使用者按下滑鼠 → DragGestureRecognizer 識別手勢 → DragSource 啟動拖曳 → 使用者移動至目標 → DropTarget 觸發 dragEnter/dragOver/drop → 完成資料傳輸。
以下範例建立一個視窗,當使用者從檔案總管拖曳圖片檔案放入時,自動在內部視窗中顯示圖片。
public class ImageDropTargetDemo {
private static final int DESKTOP_W = 800;
private static final int DESKTOP_H = 600;
private static final int OFFSET = 30;
private final JFrame frame = new JFrame("拖放目標示例:拖入圖片檔案");
private final JDesktopPane desktop = new JDesktopPane();
private int nextX = 0, nextY = 0;
private final int frameW = DESKTOP_W / 2;
private final int frameH = DESKTOP_H / 2;
public void init() {
desktop.setPreferredSize(new Dimension(DESKTOP_W, DESKTOP_H));
// 關鍵:將 JFrame 註冊為 DropTarget
new DropTarget(frame, DnDConstants.ACTION_COPY_OR_MOVE, new ImageDropHandler());
frame.add(desktop);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private class ImageDropHandler extends DropTargetAdapter {
@Override
public void drop(DropTargetDropEvent event) {
// 1. 接受複製或移動操作
event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
// 2. 取得 Transferable 物件
Transferable transferable = event.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
// 3. 遍歷支援的資料格式
for (DataFlavor flavor : flavors) {
try {
// 4. 判斷是否為 Java 檔案列表格式
if (flavor.equals(DataFlavor.javaFileListFlavor)) {
@SuppressWarnings("unchecked")
List<File> fileList = (List<File>) transferable.getTransferData(flavor);
for (File file : fileList) {
showImage(file);
}
event.dropComplete(true);
return; // 處理完畢
}
} catch (Exception ex) {
ex.printStackTrace();
event.dropComplete(false);
return;
}
}
// 無支援格式
event.rejectDrop();
}
private void showImage(File file) {
try {
BufferedImage image = ImageIO.read(file);
if (image == null) {
JOptionPane.showInternalMessageDialog(desktop, "不支援的圖片格式:" + file.getName());
return;
}
JInternalFrame iframe = new JInternalFrame(file.getName(), true, true, true, true);
JLabel label = new JLabel(new ImageIcon(image));
iframe.add(new JScrollPane(label));
desktop.add(iframe);
iframe.reshape(nextX, nextY, frameW, frameH);
iframe.setVisible(true);
nextX += OFFSET; nextY += OFFSET;
if (nextX + frameW > desktop.getWidth()) nextX = 0;
if (nextY + frameH > desktop.getHeight()) nextY = 0;
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new ImageDropTargetDemo().init());
}
}
new DropTarget(Component, int actions, DropTargetListener) 綁定目標組件、接受動作類型、監聽器java.util.List<File>JDesktopPane 實現多圖片視窗排列以下範例讓 JLabel 成為拖曳源,使用者可將其文字內容拖放到支援 DnD 的編輯器(如 IntelliJ IDEA、Eclipse、VS Code、WordPad)。注意:Windows 記事本預設不支援文字拖放。
public class TextDragSourceDemo {
public void init() {
JFrame frame = new JFrame("DragSource 示例:將文字拖至外部編輯器");
JLabel sourceLabel = new JLabel(
"AWT 拖放支援演示
按住滑鼠左鍵拖動此標籤文字到支援 DnD 的編輯器(IDE、WordPad 等)");
sourceLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
sourceLabel.setOpaque(true);
sourceLabel.setBackground(Color.LIGHT_GRAY);
// 1. 取得預設 DragSource
DragSource dragSource = DragSource.getDefaultDragSource();
// 2. 為組件建立拖曳手勢識別器
dragSource.createDefaultDragGestureRecognizer(
sourceLabel,
DnDConstants.ACTION_COPY_OR_MOVE,
new DragGestureListener() {
@Override
public void dragGestureRecognized(DragGestureEvent dge) {
// 3. 封裝文字為 Transferable
String text = sourceLabel.getText().replaceAll("<[^>]+>", ""); // 移除 HTML 標籤
Transferable transferable = new StringSelection(text);
// 4. 啟動拖曳:指定游標與 Transferable
dge.startDrag(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR), transferable);
}
});
frame.add(new JScrollPane(sourceLabel));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TextDragSourceDemo().init());
}
}
dragGestureRecognizedTransferable,自動支援 DataFlavor.stringFlavor 與 plainTextFlavorACTION_COPY、ACTION_MOVE、ACTION_LINK、ACTION_COPY_OR_MOVEJDK 1.4 起,Swing 文字組件(JTextArea、JTextField、JEditorPane 等)內建拖放支援,預設關閉,需呼叫 setDragEnabled(true) 啟用。啟用後:
public class SwingBuiltInDnDDemo {
public void init() {
JFrame frame = new JFrame("Swing 內建 DnD:文字區域拖放");
JTextArea textArea = new JTextArea(10, 40);
JTextField textField = new JTextField(30);
textArea.setText("Swing 內建拖放支援演示\n" +
"1. 選取文字拖曳到下方文字框或外部編輯器\n" +
"2. 從外部拖曳文字放入此區域\n" +
"3. 單行文字框同樣支援拖放");
// 關鍵:啟用拖放
textArea.setDragEnabled(true);
textField.setDragEnabled(true);
frame.add(new JScrollPane(textArea), BorderLayout.CENTER);
frame.add(textField, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new SwingBuiltInDnDDemo().init());
}
}
實作 Transferable 介面,在 getTransferDataFlavors() 回傳支援的 DataFlavor 陣列,getTransferData() 根據請求格式回傳對應資料(如同時支援純文字、HTML、RTF)。
實作 DragSourceListener 監聽 dragDropEnd 判斷拖曳是否成功(event.getDropSuccess()),執行後續邏輯(如移動操作需從源刪除資料)。
public class FullDropTargetListener implements DropTargetListener {
@Override public void dragEnter(DropTargetDragEvent dtde) { /* 檢查格式、設定接受動作 */ }
@Override public void dragOver(DropTargetDragEvent dtde) { /* 更新游標反饋 */ }
@Override public void dropActionChanged(DropTargetDragEvent dtde) { }
@Override public void dragExit(DropTargetEvent dte) { /* 清理視覺反饋 */ }
@Override public void drop(DropTargetDropEvent dtde) { /* 執行資料傳輸 */ }
}
Transferable 同時被剪貼簿(Clipboard)使用,實作一次即支援複製/貼上與拖放。
| 類別/介面 | 角色 | 典型用法 |
|---|---|---|
| DropTarget | 放置目標 | 接收檔案、圖片、文字等外部資料 |
| DragSource | 拖曳源 | 提供組件資料供外部應用拖放 |
| Transferable | 資料載體 | 封裝多種 DataFlavor(StringSelection, FileList, Image 等) |
| DataFlavor | 資料格式 | javaFileListFlavor, stringFlavor, imageFlavor, 自訂 MIME 類型 |
| JTextComponent.setDragEnabled(true) | 內建支援 | 一鍵啟用文字組件雙向拖放 |
掌握 AWT DnD 框架核心類別與 Swing 內建支援,即可構建豐富的拖放交互體驗。建議從 setDragEnabled(true) 開始體驗,再逐步實作自訂 DropTarget 處理檔案放置,最後掌握 DragSource 輸出自訂格式資料。