Qt 4.x:如何实现拖放到桌面或文件夹?

20

我用Qt 4.x使用C++编写了一个小型文件传输应用程序......它登录到服务器,显示用户在服务器上可用的文件列表,并允许用户上传或下载文件。

这一切都很好;甚至可以从桌面(或打开的文件夹)中拖入文件,并将文件图标拖到服务器文件列表视图中时,会自动将该文件上传到服务器。

现在我收到了相反的请求......我的用户希望能够从服务器文件列表视图中拖出文件并放到桌面或打开的文件夹窗口中,使得该文件被下载到该位置。

这似乎是个合理的要求,但我不知道如何实现。是否有一种方式让Qt应用程序查找与“拖放事件”发生时的目录相对应的目录? Ideally希望是基于Qt的平台中立机制,但如果不存在,则适用于MacOS/X和Windows(XP或更高版本)的特定于平台的机制也可以。

有任何想法吗?


1
看我的回答,你不能轻松地跨平台完成它。例如,Windows需要一个shell扩展,除非你将文件下载到临时目录,然后让Windows将其复制到正确的位置。 - Adam W
正如@atamanroman所问,你找到解决方案了吗? - Thomas Ayoub
@Samoth 检查一下7zip的功能。 - atamanroman
@JeremyFriesner,你成功完成了吗?我现在也遇到了完全相同的问题,而谷歌并没有提供太多帮助。 - Sean
2
@JeremyFriesner 嗨,我发布了一个类似的问题,我认为我可能在FileZilla的源代码中找到了一个可能的解决方案。以防你还感兴趣,这是链接:https://dev59.com/Spnga4cB1Zd3GeqPbJwC#38846300 - Sean
显示剩余4条评论
3个回答

7

请查看QMimeData及其文档,它具有一个虚拟函数。

virtual QVariant retrieveData  ( const QString & mimetype, QVariant::Type type ) const

这意味着当你拖到外面时,你需要相应地实现这些功能。
class DeferredMimeData : public QMimeData
{
  DeferredMimeData(QString downloadFilename) : m_filename(downloadFilename)

  virtual QVariant retrieveData (const QString & mimetype, QVariant::Type type) const 
  {
    if (mimetype matches expected && type matches expected)
    {
       perform download with m_filename
    }
  }
}
延迟编码示例展示了这个原则。
您可能还需要覆盖hasFormatformats以提供适当的类型,application/octet-stream可能是最常用的类型,您可能需要详细了解Windows如何使用MIME类型处理拖放操作。
我不知道您将如何提供保存文件的文件名,但您可能需要涉及到Windows方面的事情。查看QWindowsMime源代码也可能有所帮助。这可能是一个多步骤的过程,您将收到请求获取文件名的text/uri-list数据,然后再获取数据的application/octet-stream数据。
希望这可以帮到您。

这似乎几乎可以工作,但我怀疑它会冻结我的应用程序的GUI,直到retrieveData()返回。 鉴于下载可能需要很长时间(几分钟或几小时),这将是次优的。 :/ - Jeremy Friesner
1
我认为这并不排除在单独的线程中进行下载或让GUI通过QCoreApplication :: processEvents()处理事件。不过我必须承认,我试图修改延迟编码示例以将文件存储在桌面上,但并没有取得太大的成功。 - Harald Scheirich
一个单独的线程并不能解决问题,因为retrieveData()仍然需要阻塞,因为它将数据作为返回值返回,并且在下载完成之前无法返回。从retrieveData()中调用processEvents()可能会起作用,但我不会相信它...我以前曾经被这种技术所伤。 - Jeremy Friesner
4
供其他人研究此问题的参考,2014年的延迟编码示例现已移至:http://qt-project.org/doc/qt-4.8/draganddrop-delayedencoding.html。其中的关键解决方法是:使用自定义的MimeData类,在其retrieveData方法被调用时发出信号。这样就可以找到拖放操作的结束位置。 - user405
发射信号如何帮助解决即使在retrieveData方法内部,您也无法知道用户放置项目的路径的问题?仅提供typemimeType参数。https://doc.qt.io/qt-5/qmimedata.html#retrieveData - pyjamas

3

我认为你的方法不太对。

你并不关心拖放操作的目标位置,只知道有一个文件被拖放了。在DropEvent中,将文件下载到临时位置,然后将mime数据设置为下载的内容。尽管这可能会导致硬盘上出现第二份副本,但它从一开始就是跨平台的。之后你可以使用特定于平台的调用进行优化。

看看Dropsite示例,了解其他来源的mime数据的工作原理...

编辑:

如果你不编写Windows shell扩展(至少对于Windows来说),似乎双重复制方法是标准的做法。Filezilla和7zip都这样做,它们返回一个带有临时位置的"text/uri-list" mime类型。这样,资源管理器将数据从临时位置复制到实际位置。你的应用程序也可以这样做,创建一个没有数据的临时文件(或仅名称)。使用延迟编码示例,在拖放时创建数据。整个操作似乎非常依赖于平台。在Linux(运行KDE)上,我可以仅基于mime类型进行拖放操作。看起来Windows没有这么灵活。


1
请在点踩之前阅读。Dropsite示例向您展示如何使用来自任何来源的MIME类型。您可以启动程序,然后将其拖放到该程序上以查看列出的MIME类型。至于第一部分,您让操作系统处理拖放。您提供它MIME类型和数据,它会将其放置在您拖放的位置。就像从一个资源管理器窗口拖动项目到另一个窗口一样。请阅读拖放文档以及http://bit.ly/bDooQe。 - Adam W
1
我已经阅读了它,但我认为答案并不适用于手头的问题。特别是,操作系统不知道如何下载文件,因此无法处理拖放。我无法直接在拖动对象中包含文件数据,因为文件仅在拖动时存在于服务器上。我无法在拖动之前或期间下载数据,因为文件可能很大(例如几个千兆字节)。 - Jeremy Friesner
1
如果你查看:http://doc.trolltech.com/4.6/draganddrop-delayedencoding.html,并且将文件从资源管理器拖放到示例中,你可能会找到所需的魔法组合的MIME类型来创建文件。你可能需要触发mouserlease事件而不是dropevent,因为正如你所说,你无法获取拖放事件。 - Adam W
1
我认为问题仍然存在,因为我不能在瞬间“创建数据”,因为数据并不存在于本地。我必须下载数据,这个过程可能需要很长时间(即几分钟/几小时)...太长了,以至于无法让retrieveData()阻塞直到下载完成。 - Jeremy Friesner
1
你想让下拉操作触发下载,但是你会在GUI上进行管理吗?你只需要目标路径,这样你就可以配置你的软件将文件保存在那里了? - Spidey
显示剩余4条评论

2
你只需要实现拖拽部分,不需要知道放置发生在哪里,因为接收到的应用程序会处理它,并决定在哪里存储你的文件。
你创建一个QMimeData,不确定是哪种类型,但从这里这里看,可能是带有你的文件数据的QByteArray的"application/octet-stream"或者指向你的文件的url的"text/uri-list"。
你创建一个QDrag,并使用它的setMimeData()方法和exec()(不确定选择哪个QT::DropAction)。
下面是一个示例(免责声明:我做的是颜色而不是文件):
void DraggedWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        start_pos = event->pos();
}

void DraggedWidget::mouseMoveEvent(QMouseEvent *event)
{    
    if (event->buttons() & Qt::LeftButton) {
        int distance = (event->pos() - start_pos).manhattanLength();
        if (distance >= QApplication::startDragDistance())
        {
            /* Drag */
            QMimeData *mime_data = new QMimeData;

            mime_data->setData( ... );

            QDrag *drag = new QDrag(this);
            drag->setMimeData(mime_data);
            drag->exec(Qt::CopyAction);
        }
    }
}

抱歉,这段话非常不完整,但我希望它能起到一点帮助。

1
问题在于文件中的数据直到下载完成后才可用,而多千兆字节的下载可能需要一个小时甚至更长时间。因此,我需要知道拖放发生的位置,以便我的应用程序可以启动一个下载线程来将数据写入该位置。如果一个系统依赖于数据在拖动时已经存在于本地,那么这对我是行不通的,据我所知。包括文件的URI可能会起作用,但前提是该文件可以通过操作系统知道如何下载的协议(例如HTTP)进行访问。目前服务器使用专有协议。嗯... - Jeremy Friesner

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