【问题标题】:PyQt5 drag and drop into system file explorer (with delayed encoding)?PyQt5拖放到系统文件资源管理器(延迟编码)?
【发布时间】:2020-10-07 21:36:17
【问题描述】:

我想让用户通过将项目从 PyQt 拖到系统文件资源管理器来创建文件。由于某些文件会非常大,我还需要延迟将数据设置为用户完成拖放时,而不是立即开始拖动。

这个例子好像是我需要的:https://doc.qt.io/archives/4.6/draganddrop-delayedencoding.html

我尝试将其转换为一个简单的 PyQt5 版本,其中将 QPushButton 拖到文件夹中将创建一个纯文本文件,但它对我不起作用...当我运行它时,它没有任何作用,我的光标看起来像这样:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import typing
import time


class MimeData(QtCore.QMimeData):
    dataRequested = QtCore.pyqtSignal(str)

    def formats(self) -> typing.List[str]:
        return QtCore.QMimeData.formats(self) + ["text/plain"]

    def retrieveData(self, mime_type: str, preferred_type: QtCore.QVariant.Type):
        self.dataRequested.emit(mime_type)
        return QtCore.QMimeData.retrieveData(self, mime_type, preferred_type)


class SourceWidget(QtWidgets.QWidget):
    mimeData: MimeData = None

    def __init__(self, parent=None):
        super().__init__(parent)

        layout = QtWidgets.QVBoxLayout()
        button = QtWidgets.QPushButton("Drag Me")
        button.pressed.connect(self.start_drag)

        layout.addWidget(button)
        self.setLayout(layout)

    @QtCore.pyqtSlot()
    def create_data(self, mime_type):
        if mime_type == "text/plain":
            time.sleep(0.25)  # Simulate large file
            self.mimeData.setData("text/plain", b"my text file contents")

    @QtCore.pyqtSlot()
    def start_drag(self):
        self.mimeData = MimeData()
        self.mimeData.dataRequested.connect(self.create_data)

        drag = QtGui.QDrag(self)
        drag.setMimeData(self.mimeData)
        drag.exec(QtCore.Qt.CopyAction)


if __name__ == "__main__":
    app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)

    w = SourceWidget()
    w.show()

    app.exec_()

【问题讨论】:

  • 不幸的是,我不认为你可以有一个简单的答案(至于你的original question。在 Qt 之外拖放并不容易实现,因为它完全取决于拖放的平台管理。长话短说:你不能用 PyQt 做到这一点;至少,不容易。尽管如此,你的代码中仍然存在一些问题,最重要的是你使用了没有参数的 pyqtSlot() 装饰器,但 create_data 需要一个字符串。
  • 可以尝试做的是创建一个临时文件(如果源还不存在),并将该临时文件的 url 添加到 mimeData url列表。同时,就在开始拖动操作之前,您可以创建一个单独创建文件的线程,并使用一个简单的while循环等待直到线程被(成功完成。 PS:关于pyqtSlot() 问题,请记住这些装饰器很少需要,所以如果你真的不需要它们(如本例所示),请避免使用它们。
  • 嗯,该链接显示了如何设置包含图像的 mimeData;平台对此做什么完全取决于平台如何根据拖动数据管理拖放操作。我不知道您使用的是什么平台,我猜您在 Windows 下(您从未指定过,这在某种程度上很重要),我只能假设该平台会自动建议 根据拖拽的内容用图像数据创建一个新文件。事实上,如果仔细观察,createData 函数只是更新 mime 内容,并没有实际创建文件。
  • 我不知道你要创建什么样的数据,但如果是自定义的,我相信唯一的解决方案是用你要创建的数据创建一个临时文件使用,为该文件提供适当的QUrl,并以在删除完成后立即删除源文件的方式连接放置操作。
  • 好吧,看起来你不需要太多的工作:只需使用 python 自己的with open(path, 'w') as f: f.write(data) 并在之后返回路径,然后使用fromLocalFile(path) 构造 QUrl并将 URL 的路径添加为列表。我不知道 Qt 在 Windows 下的行为如何;如果 dropEvent 在将数据传递给系统后立即返回(就像我认为的那样),您需要找到一种方法来确保在删除临时文件之前已完成复制,但请注意您必须考虑->

标签: python pyqt pyqt5


【解决方案1】:

这是我最终将文件从 PyQt5 拖放到文件资源管理器中的结果,并且只有在释放鼠标后才写入文件以完成拖放。


import time

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt

import tempfile
import os

# Use win32api on Windows because the pynput and mouse packages cause lag
# https://github.com/moses-palmer/pynput/issues/390
if os.name == 'nt':
    import win32api


    def mouse_pressed():
        return win32api.GetKeyState(0x01) not in [0, 1]
else:
    import mouse


    def mouse_pressed():
        return mouse.is_pressed()


class DelayedMimeData(QtCore.QMimeData):
    def __init__(self):
        super().__init__()
        self.callbacks = []

    def add_callback(self, callback):
        self.callbacks.append(callback)

    def retrieveData(self, mime_type: str, preferred_type: QtCore.QVariant.Type):
        mp = mouse_pressed()
        if not mp:
            for callback in self.callbacks.copy():
                self.callbacks.remove(callback)
                callback()

        return QtCore.QMimeData.retrieveData(self, mime_type, preferred_type)


class Navigator(QtWidgets.QTreeWidget):
    def __init__(self):
        super().__init__()

        self.setHeaderLabels(["Name"])
        QtWidgets.QTreeWidgetItem(self, ['Test1'])
        QtWidgets.QTreeWidgetItem(self, ['Test2'])
        QtWidgets.QTreeWidgetItem(self, ['Test3'])

        self.setAcceptDrops(True)
        self.setDragEnabled(True)
        self.setDragDropMode(self.DragDrop)
        self.setDefaultDropAction(Qt.MoveAction)
        self.setSelectionMode(self.ExtendedSelection)
        self.setSelectionBehavior(self.SelectRows)

        self.setContextMenuPolicy(Qt.CustomContextMenu)

    def startDrag(self, actions):
        drag = QtGui.QDrag(self)
        names = [item.text(0) for item in self.selectedItems()]
        mime = DelayedMimeData()
        path_list = []
        for name in names:
            path = os.path.join(tempfile.gettempdir(), 'DragTest', name + '.txt')
            os.makedirs(os.path.dirname(path), exist_ok=True)
            print(path)

            def write_to_file(path=path, name=name, widget=self):
                with open(path, 'w+') as f:
                    print("Writing large file(s)...")
                    time.sleep(2)  # Sleep to simulate long file write
                    f.write(f"Contents of {name}")

            mime.add_callback(write_to_file)
            path_list.append(QtCore.QUrl.fromLocalFile(path))

        mime.setUrls(path_list)
        mime.setData('application/x-qabstractitemmodeldatalist',
                     self.mimeData(self.selectedItems()).data('application/x-qabstractitemmodeldatalist'))
        drag.setMimeData(mime)
        drag.exec_(Qt.MoveAction)
        super().startDrag(actions)


app = QtWidgets.QApplication([])

nav = Navigator()
nav.show()
app.exec_()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    相关资源
    最近更新 更多