drop发生在application外部时,如何获取drop target?
How to get the drop target when the drop occurs outside the application?
我有一个带有 QTreeWidget 的 python qt5 程序。每个 QTreeWidgetItem 代表文件。我的逻辑在 mouseMoveEvent 中正常工作,因此当用户将文件 mime 数据拖动到 windows 资源管理器或桌面时,QDrag CopyAction 成功地将文件 mime 数据传递给 windows。但是,我需要知道目标路径是什么。如果用户在应用程序内拖动,QDrag target() 函数仅 returns 一个小部件。如果用户拖到应用程序外部(如桌面),则 target() returns None.
有没有办法知道用户在 windows 中拖到的路径?
我需要知道目标路径的原因是因为文件实际上会在运行时生成,并且生成可能需要一分钟才能完成。在这个 mvce 示例中,我使用 touch 命令立即创建一个空文件,这样用户就不必等待或看到长时间的延迟。然后在一分钟后生成实际文件后,我想用实际文件替换空文件。然后我的应用程序可以告诉用户该文件现在可用。我无法预先生成文件,因为列表中会有很多文件,每个文件都需要几分钟才能生成。
这是我的 mvce(最小可重现示例):
example.py
#!python3
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QTreeWidgetItem
from main_ui import Ui_MainWindow
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent) # initialization of the superclass
self.setupUi(self) # setup the GUI --> function generated by pyuic4
self.tree1.blockSignals(True)
self.tree1.clear()
self.tree1.setHeaderHidden(True)
self.tree1.setColumnCount(2)
self.tree1.setRootIsDecorated(True) # show expansion icon
level1 = QTreeWidgetItem(self.tree1)
level1.setText(0, "root")
level1.setExpanded(True)
level2 = QTreeWidgetItem(level1)
level2.setExpanded(False)
level2.setText(0, "dragme to desktop")
level2.setExpanded(True)
self.tree1.show()
self.tree1.resizeColumnToContents(0)
self.tree1.blockSignals(False)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
myapp = MainWindow() # instantiate the main window
rc = app.exec_()
sys.exit(rc) # exit with the same return code of Qt application
main.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>415</width>
<height>451</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="mytree" name="tree1">
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="autoScrollMargin">
<number>16</number>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="columnCount">
<number>0</number>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>100</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>mytree</class>
<extends>QTreeWidget</extends>
<header>mytree.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
mytree.py
#!python3
import tempfile
from pathlib import Path
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
class mytree(QtWidgets.QTreeWidget): # mytree is now a subclass of qlineedit class
def __init__(self, parent=None):
super(mytree, self).__init__(parent) # The use of "super" is a slightly better method of calling the parent for initialization
self.setDragEnabled(True)
def mouseMoveEvent(self, event): # is called whenever the mouse moves while a mouse button is held down
super(mytree, self).mouseMoveEvent(event) #propagate
tempdir = tempfile.gettempdir() # temp directory aka %temp%
realfile = tempdir + "/empty.txt"
Path(realfile).touch() # create empty file
fileurl = "file:///%s" % realfile # build url
url = QtCore.QUrl(fileurl)
urllist = []
urllist.append(url) # only 1 file
drag = QtGui.QDrag(self)
md = QtCore.QMimeData()
md.setUrls(urllist)
drag.setMimeData(md)
result = drag.exec_(Qt.CopyAction)
source = drag.source()
print("source = %s" % source)
target = drag.target() # returns widget if inside the application, returns None if windows desktop ... etc.
print("target = %s" % target)
原因
QDrag::target
return 是指向 QObject
的指针。在 Qt 小部件应用程序中,这是有道理的,因为所有可能的放置目标都是某种 QWidget
,即 QObject
。当放置操作在应用程序外部时,return 除了空指针之外的任何东西都没有意义,因为:
如果不是,QObject
应该 target
return 指向的指针?
如果您以某种方式设法获得某种指针,您会将其转换为哪种类型以访问您需要的信息,因为 QObject 绝对不包含任何文件 URL?
我承认QDrag::target
is not as explicit on this, as the documentation of QDropEvent::source
的文档:
Returns the target of the drag and drop operation. This is the widget where the drag object was dropped.
与
相比
If the source of the drag operation is a widget in this application, this function returns that source; otherwise it returns nullptr.
然而,This is the widget 部分给出了关于这个事实的提示,目标应该是一个 widget。
解决方案
使用 QFileDialog::getSaveFileUrl
代替拖动功能来获取文件的 url。
解决方法:QFileSystemModel
根据您的要求,解决方案可能是通过在 QTreeView
.
中呈现 QFileSystemModel
来在您的应用程序中包含一个文件系统树
使用这种方法,拖放操作完全在您的应用程序内部处理,这样您就可以提取目标路径,如果需要,还可以在后台直接生成文件,而不是使用空文件首先.
跟踪移动的空文件
一个可能的解决方案是让拖放操作移动一个空文件,跟踪这个文件的移动(重命名)操作。但是,我不确定以跨平台方式使用 Python and/or 是否可行。
无法使用 QFileSystemWatcher
执行此跟踪,因为它不会报告新文件位置。 This thread 讨论了主要平台上的可能实现:
inotify
Unix
ReadDirectoryChangesW
在 Windows
- https://apple.stackexchange.com/questions/126344/how-does-mac-os-keep-track-of-moved-files 对于 mac
我有一个带有 QTreeWidget 的 python qt5 程序。每个 QTreeWidgetItem 代表文件。我的逻辑在 mouseMoveEvent 中正常工作,因此当用户将文件 mime 数据拖动到 windows 资源管理器或桌面时,QDrag CopyAction 成功地将文件 mime 数据传递给 windows。但是,我需要知道目标路径是什么。如果用户在应用程序内拖动,QDrag target() 函数仅 returns 一个小部件。如果用户拖到应用程序外部(如桌面),则 target() returns None.
有没有办法知道用户在 windows 中拖到的路径?
我需要知道目标路径的原因是因为文件实际上会在运行时生成,并且生成可能需要一分钟才能完成。在这个 mvce 示例中,我使用 touch 命令立即创建一个空文件,这样用户就不必等待或看到长时间的延迟。然后在一分钟后生成实际文件后,我想用实际文件替换空文件。然后我的应用程序可以告诉用户该文件现在可用。我无法预先生成文件,因为列表中会有很多文件,每个文件都需要几分钟才能生成。
这是我的 mvce(最小可重现示例):
example.py
#!python3
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QTreeWidgetItem
from main_ui import Ui_MainWindow
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent) # initialization of the superclass
self.setupUi(self) # setup the GUI --> function generated by pyuic4
self.tree1.blockSignals(True)
self.tree1.clear()
self.tree1.setHeaderHidden(True)
self.tree1.setColumnCount(2)
self.tree1.setRootIsDecorated(True) # show expansion icon
level1 = QTreeWidgetItem(self.tree1)
level1.setText(0, "root")
level1.setExpanded(True)
level2 = QTreeWidgetItem(level1)
level2.setExpanded(False)
level2.setText(0, "dragme to desktop")
level2.setExpanded(True)
self.tree1.show()
self.tree1.resizeColumnToContents(0)
self.tree1.blockSignals(False)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
myapp = MainWindow() # instantiate the main window
rc = app.exec_()
sys.exit(rc) # exit with the same return code of Qt application
main.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>415</width>
<height>451</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="mytree" name="tree1">
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="autoScrollMargin">
<number>16</number>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="columnCount">
<number>0</number>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>100</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>mytree</class>
<extends>QTreeWidget</extends>
<header>mytree.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
mytree.py
#!python3
import tempfile
from pathlib import Path
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
class mytree(QtWidgets.QTreeWidget): # mytree is now a subclass of qlineedit class
def __init__(self, parent=None):
super(mytree, self).__init__(parent) # The use of "super" is a slightly better method of calling the parent for initialization
self.setDragEnabled(True)
def mouseMoveEvent(self, event): # is called whenever the mouse moves while a mouse button is held down
super(mytree, self).mouseMoveEvent(event) #propagate
tempdir = tempfile.gettempdir() # temp directory aka %temp%
realfile = tempdir + "/empty.txt"
Path(realfile).touch() # create empty file
fileurl = "file:///%s" % realfile # build url
url = QtCore.QUrl(fileurl)
urllist = []
urllist.append(url) # only 1 file
drag = QtGui.QDrag(self)
md = QtCore.QMimeData()
md.setUrls(urllist)
drag.setMimeData(md)
result = drag.exec_(Qt.CopyAction)
source = drag.source()
print("source = %s" % source)
target = drag.target() # returns widget if inside the application, returns None if windows desktop ... etc.
print("target = %s" % target)
原因
QDrag::target
return 是指向 QObject
的指针。在 Qt 小部件应用程序中,这是有道理的,因为所有可能的放置目标都是某种 QWidget
,即 QObject
。当放置操作在应用程序外部时,return 除了空指针之外的任何东西都没有意义,因为:
如果不是,
QObject
应该target
return 指向的指针?如果您以某种方式设法获得某种指针,您会将其转换为哪种类型以访问您需要的信息,因为 QObject 绝对不包含任何文件 URL?
我承认QDrag::target
is not as explicit on this, as the documentation of QDropEvent::source
的文档:
Returns the target of the drag and drop operation. This is the widget where the drag object was dropped.
与
相比If the source of the drag operation is a widget in this application, this function returns that source; otherwise it returns nullptr.
然而,This is the widget 部分给出了关于这个事实的提示,目标应该是一个 widget。
解决方案
使用 QFileDialog::getSaveFileUrl
代替拖动功能来获取文件的 url。
解决方法:QFileSystemModel
根据您的要求,解决方案可能是通过在 QTreeView
.
QFileSystemModel
来在您的应用程序中包含一个文件系统树
使用这种方法,拖放操作完全在您的应用程序内部处理,这样您就可以提取目标路径,如果需要,还可以在后台直接生成文件,而不是使用空文件首先.
跟踪移动的空文件
一个可能的解决方案是让拖放操作移动一个空文件,跟踪这个文件的移动(重命名)操作。但是,我不确定以跨平台方式使用 Python and/or 是否可行。
无法使用 QFileSystemWatcher
执行此跟踪,因为它不会报告新文件位置。 This thread 讨论了主要平台上的可能实现:
inotify
UnixReadDirectoryChangesW
在 Windows- https://apple.stackexchange.com/questions/126344/how-does-mac-os-keep-track-of-moved-files 对于 mac