如何在Swing中使用拖放功能获取文件路径?

49

我的Swing应用程序中有一个JTextField,用于保存所选文件的文件路径。目前我使用JFileChooser来填充该值。但是,我希望用户能够拖放文件到这个JTextField上,并将该文件的文件路径放入JTextField中,而不总是使用JFileChooser

如何实现这一点?

5个回答

47

如果您不想花费太多时间研究这个比较复杂的主题,并且您使用的是Java 7或更高版本,则以下是一个快速示例,演示如何通过将JTextArea设置为拖放目标来接受拖放的文件:

JTextArea myPanel = new JTextArea();
myPanel.setDropTarget(new DropTarget() {
    public synchronized void drop(DropTargetDropEvent evt) {
        try {
            evt.acceptDrop(DnDConstants.ACTION_COPY);
            List<File> droppedFiles = (List<File>)
                evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
            for (File file : droppedFiles) {
                // process files
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
});

2
这应该是被接受的答案。两个评论:首先,在接受拖放之前,您应该检查传输数据的类型;其次,当您完成任何与拖放相关的动画时,应调用evt.dropComplete(true)来完成拖放,否则,尽管拖放起作用,但对用户来说看起来很奇怪。 - Christopher Schultz

45

首先,你应该查看Swing DragDrop支持。之后,对于不同的操作系统,有一些小技巧。一旦你开始工作,就会处理drop()回调。在这个回调中,你需要检查Transferable的DataFlavor。

对于Windows,你可以简单地检查DataFlavor.isFlavorJavaFileListType(),然后像这样获取数据

List<File> dropppedFiles = (List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor)

对于Linux(可能还有Solaris),DataFlavor会有一点棘手。你需要自己创建DataFlavor,而Transferable类型也会不同。

nixFileDataFlavor = new DataFlavor("text/uri-list;class=java.lang.String");
String data = (String)transferable.getTransferData(nixFileDataFlavor);
for(StringTokenizer st = new StringTokenizer(data, "\r\n"); st.hasMoreTokens();)
{
    String token = st.nextToken().trim();
    if(token.startsWith("#") || token.isEmpty())
    {
         // comment line, by RFC 2483
         continue;
    }
    try
    {
         File file = new File(new URI(token))
         // store this somewhere
    }
    catch(...)
    {
       // do something good
       ....
    }
}

1
+1 非常感谢! :) 然而,Java7不需要这个技巧,真是太好了。 - Oleg Kuznetsov
@Oleg:你的意思是说,Java 7中的DataFlavor将不考虑平台而统一使用javaFileListFlavor吗? - Adamski
6
是的,在Java 7 JRE中,使用javaFileListFlavor就足够了(至少在Windows和Linux上)。 - Oleg Kuznetsov
阅读了DragDrop链接后,我发现这个页面帮助我为我的自定义JComponent非常高效地支持拖放:https://docs.oracle.com/javase/tutorial/uiswing/dnd/toplevel.html(我所要做的就是实现处理程序)。 - Perry Monschau

34

有一个示例程序,其中包含一个可用于简化文件和文件夹拖放的类:

http://www.iharder.net/current/java/filedrop/

我在Windows 7和Ubuntu 10.10上进行了测试,在这两个环境中都表现良好。

要使用它,您需要将以下代码添加到您的代码中:

  JPanel  myPanel = new JPanel();
  new  FileDrop( myPanel, new FileDrop.Listener()
  {   public void  filesDropped( java.io.File[] files )
      {   
          // handle file drop
          ...
      }   // end filesDropped
  }); // end FileDrop.Listener

@Joe - 来自网站的信息:“任何java.awt.Component都可以被拖放,但只有javax.swing.JComponents会用改变边框的方式表示拖放事件。”- 鉴于JTable是JComponent,我想,它完全可以使用。 - ArtOfWarfare
有趣,现在又可以工作了。一定是由于最新的Windows更新。 - phil294

23

我知道这是一个老问题,但目前的回答都有点过时:

  • 从 JDK 1.6 开始,应该使用 'TransferHandler' 类,并使用新的(覆盖的)方法
  • 对 Linux 的支持变得更好了,不需要自定义处理

这在 Linux(KDE5)和 Windows 7 上都可行:

final class FileDropHandler extends TransferHandler {
    @Override
    public boolean canImport(TransferHandler.TransferSupport support) {
        for (DataFlavor flavor : support.getDataFlavors()) {
            if (flavor.isFlavorJavaFileListType()) {
                return true;
            }
        }
        return false;
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean importData(TransferHandler.TransferSupport support) {
        if (!this.canImport(support))
            return false;

        List<File> files;
        try {
            files = (List<File>) support.getTransferable()
                    .getTransferData(DataFlavor.javaFileListFlavor);
        } catch (UnsupportedFlavorException | IOException ex) {
            // should never happen (or JDK is buggy)
            return false;
        }

        for (File file: files) {
            // do something...
        }
        return true;
    }
}

可在任何组件上使用

myComponent.setTransferHandler(new FileDropHandler());

1
我在我的Mac上运行了OS“High Sierra”,完美地运行了! - trinity420
canImport 中,您会遍历数据类型以检查是否支持 isFlavorJavaFileListType,但在 importData 中,您使用 getTransferData(DataFlavor.javaFileListFlavor) 而不是遍历所有的数据类型。为什么不在 canImport 中使用 support.isDataFlavorSupported(DataFlavor.javaFileListFlavor) 或者在 importData 中进行迭代并执行相同的检查呢? - Rangi Keen
@RangiKeen 迭代和使用 isFlavorJavaFileListType() 更容易 - 使用 Stream API 可以实现一行代码,而且不需要 try-catch 块。在 importData() 中,只有通过 getTransferData() 才能获取文件。 - ABika
@ABika 但是为什么要假设isFlavorJavaFileListType返回的唯一类型是DataFlavor.javaFileListFlavor呢?在没有传输数据的情况下,您在canImport中的检查是否会返回true?我期望如果您在canImport中迭代以查找支持的类型,那么您也会在importData中迭代以查找相同的类型,然后使用它来获取传输数据,而不是硬编码javaFileListFlavor。或者只需从canImport返回support.isDataFlavorSupported(DataFlavor.javaFileListFlavor) - Rangi Keen

-5

这对我很有效。我正在像这样使用它(scala):

def onDrop(files: List[java.io.File]): Unit = { ... }

    val lblDrop = new Label {
      peer.setTransferHandler(new FileDropHandler(onDrop))
      border = EtchedBorder
    }



class FileDropHandler(val onDrop: List[java.io.File] => Unit) extends javax.swing.TransferHandler {
      import javax.swing.JComponent
      import java.awt.datatransfer.{Transferable, DataFlavor}
        import java.net.URI
    import java.io.File

    val stdFileListFlavor = DataFlavor.javaFileListFlavor
    val nixFileListFlavor = new DataFlavor("text/uri-list;class=java.lang.String")

    override def canImport(comp: JComponent, flavors: Array[DataFlavor]): Boolean =
        flavors.exists(flavor =>
            (flavor == stdFileListFlavor) ||
            (flavor == nixFileListFlavor)
        )

    override def importData(comp: JComponent, t: Transferable): Boolean = {

        val flavors = t.getTransferDataFlavors()

        val files = if (flavors.exists(_ == stdFileListFlavor)) {
            val data = t.getTransferData(stdFileListFlavor)
            importStdFileList( data )
        } else if (flavors.exists(_ == nixFileListFlavor)) {
            val data = t.getTransferData(nixFileListFlavor)
            importNixFileList( data )
        } else List()

        onDrop( files )

        !files.isEmpty
    }

    private def importStdFileList(data: Any): List[File] = {
      data.asInstanceOf[List[File]] //XXX NOT TESTED
    }

    private def importNixFileList(data: Any): List[File] = {

        def clean(rawLine: String): Option[String] = {
            val line = rawLine.trim
            if   (line.length == 0 || line == "#") None
            else                                   Some(line)
        }

        def asURI(line: String): Option[URI] = {
            try   { Some(new URI(line)) }
            catch { case e:Exception => println(e); None }
        }

        def asFile(uri: URI): Option[File] = {
            try   { Some(new File(uri)) }
            catch { case e:Exception => println(e); None }
        }

        data.asInstanceOf[java.lang.String].split("\n")
     .toList flatMap clean flatMap asURI flatMap asFile
    }
}

7
针对"The SO-Code-Sample-Button really S****"这样的评论,请不要包含在回答中,它是无用的,会干扰你实际想要解释的内容。如果你真的有问题,请在 元网站 上进行讨论,而不是在这里。 - Gnoupi
10
嘿,你在寻找Java的解决方案,让我们浪费你的时间,展示一下如何用Scala实现。 - Igor S.
4
Scala程序员也会寻找典型的Java Swing问题,毕竟我们在同一个生态系统中。这个答案并不是为了回复提问者而是为了回复那些典型的StackOverflow搜索者。希望没有浪费你之前的时间。 - hotzen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接