• category: swing folder: FileAndTextTransferHandler title: JTextComponentにファイルとテキストをドロップ可能に設定する tags: [JTextComponent, DragAndDrop, DropTargetListener, TransferHandler] author: aterai pubdate: 2022-11-21T00:21:27+09:00 description: JTextComponentにデフォルトの文字列だけでなくファイルもドロップ可能になるよう設定します。 image: https://drive.google.com/uc?id=19UPpYiiOmU-vIAHpCt35QmXdofVKz3nO

概要

JTextComponentにデフォルトの文字列だけでなくファイルもドロップ可能になるよう設定します。

サンプルコード

public void addTab(File file) {
  JTextArea textArea = new JTextArea();
  textArea.setDragEnabled(true);
  new DropTarget(textArea, DnDConstants.ACTION_COPY, new FileDropTargetListener(), true);
  if (file != null) {
    try (Reader in = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
      textArea.read(in, "File");
      tabbedPane.addTab(file.getName(), new JScrollPane(textArea));
    } catch (IOException ex) {
      UIManager.getLookAndFeel().provideErrorFeedback(textArea);
    }
  } else {
    tabbedPane.addTab("*untitled*", new JScrollPane(textArea));
  }
  tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
}

public void addFile(Transferable transferable) throws UnsupportedFlavorException, IOException {
  List<?> list = (List<?>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
  new SwingWorker<Void, Void>() {
    @Override protected Void doInBackground() {
      for (Object o : list) {
        if (o instanceof File) {
          addTab((File) o);
        }
      }
      return null;
    }
  }.execute();
}

private class FileDropTargetListener extends DropTargetAdapter {
  @Override public void drop(DropTargetDropEvent e) {
    Transferable transferable = e.getTransferable();
    if (e.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
      e.acceptDrop(DnDConstants.ACTION_COPY);
      try {
        addFile(transferable);
        e.dropComplete(true);
      } catch (UnsupportedFlavorException | IOException ex) {
        e.dropComplete(false);
      }
    } else {
      JTextComponent textArea = (JTextComponent) e.getSource();
      TransferHandler textHandler = textArea.getTransferHandler();
      if (textHandler != null) {
        textHandler.importData(textArea, transferable);
      }
    }
  }
}

private class FileTransferHandler extends TransferHandler {
  @Override public boolean canImport(TransferSupport support) {
    // System.out.println(support.getComponent().getClass().getName());
    boolean isDrop = support.isDrop();
    boolean supported = support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
    return supported && isDrop;
  }

  @Override public boolean importData(TransferSupport support) {
    // System.out.println(support.getComponent().getClass().getName());
    Transferable transferable = support.getTransferable();
    try {
      addFile(transferable);
      return true;
    } catch (IOException | UnsupportedFlavorException ex) {
      return false;
    }
  }
}
View in GitHub: Java, Kotlin

解説

  • FileTransferHandlerを設定したJFrame
    • 子コンポーネントやタイトルバーを含む領域にファイルをドロップ可能
      Window window = SwingUtilities.getWindowAncestor(tabbedPane);
      if (window instanceof JFrame) {
        ((JFrame) window).setTransferHandler(new FileTransferHandler());
      }
      
  • デフォルトのJTextArea
    • BasicTextUI.TextTransferHandlerがデフォルトで設定されているのでテキスト文字列を別コンポーネントなどがドロップ可能だが、JFrameに設定したFileTransferHandlerは打ち消されるためファイルのドロップは不可
    • JTextArea#setDragEnabled(true)を設定して選択文字列をドラッグ可能に設定
  • FileDropTargetListenerを追加したJTextArea
    • DropTargetAdapter#drop(...)をオーバーライドしてドロップイベントがDataFlavor.javaFileListFlavorをサポートしている場合はファイルをJTextAreaに読み込んでJTabbedPaneに追加、それ以外はJTextAreaからBasicTextUI.TextTransferHandlerを取得してTransferHandler#importData(...)を実行してテキストをドロップ
    • JTextArea#setDragEnabled(true)を設定すれば選択文字列をドラッグ可能になるが、テキストドラッグオーバー中のキャレット描画に未対応
  • JTextAreaJFrameに設定したFileTransferHandlerを設定すると、デフォルトのBasicTextUI.TextTransferHandlerが置き換えられてテキストがドロップ不可になる
  • これを回避するため、以下のようなTransferHandlerでラップしてファイルとテキストのドロップに対応する方法もあるが、JTextArea#setDragEnabled(true)を設定済みのJTextAreaでも文字列を選択してドラッグアウトが不可になる
TransferHandler textHandler = textArea.getTransferHandler();
TransferHandler fileHandler = new FileTransferHandler();
textArea.setTransferHandler(new TransferHandler() {
  @Override public boolean canImport(TransferSupport support) {
    return fileHandler.canImport(support) || textHandler.canImport(support);
  }

  @Override public boolean importData(TransferSupport support) {
    if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
      return fileHandler.importData(support);
    } else {
      return textHandler.importData(support);
    }
  }
});

参考リンク

コメント