覆盖 mouseMoveEvent() 时 dragMoveEvent() 无法正常工作 - Qt Drag&Drop
dragMoveEvent() doesn't work properly when overriding mouseMoveEvent() - Qt Drag&Drop
我正在使用 PySide2 构建一个相当复杂的 GUI,我必须为 QTreeView 小部件实现拖放系统(内部和外部移动都应被接受)。
目标
能够将项目从 QTreeView
小部件(文件资源管理器)复制到另一个 QTreeView
小部件(测试小部件)并在此测试小部件中移动项目。应检查每个拖动的文件,以使用户了解他是否可以移动该文件以及应该将其放在哪里。
此外,当带有拖动元素的鼠标悬停时,测试小部件的项目应突出显示(一个接一个)。 (实际上,我想在这些项目之间画一条线,但我现在做不到:欢迎任何建议)。
问题
外部移动完美运行,而内部移动无法正常运行:在拖放操作期间,即使应该接受移动,'stop' 图标也会始终显示。它实际上被接受了,因为 drop 操作是成功的。
悬停项目的突出显示也不起作用。
我认为问题是由覆盖测试小部件的 mouseMoveEvent()
方法引起的,我被迫实施该方法以设置 QMimeData
对象。
代码
请注意,出于隐私原因,部分代码已替换为“[...]”。无论如何,这些部分对于系统的运行并不重要。
class MyStandardItem(QStandardItem):
def __init__(self, text, icon_path='', value='', num=0, font_size=8, set_bold=False):
super().__init__()
self.setDragEnabled(True)
self.setDropEnabled(True)
self.num = num
self.value = value
self.setText(text)
font = QFont('Segoe UI', font_size)
font.setBold(set_bold)
self.setFont(font)
self.setIcon(QIcon(icon_path))
def setCheckState(self, checkState):
super().setCheckState(checkState)
if checkState == Qt.Unchecked:
self.setForeground(QColor(150, 150, 150))
def get_data(self):
return self.text(), self.value, self.num
class MyTreeView(QTreeView):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.hover_item = None
self.setMouseTracking(True)
self.start_drag_pos = None
self.model = QStandardItemModel()
self.root = self.model.invisibleRootItem()
self.setModel(self.model)
def mousePressEvent(self, event: QtGui.QMouseEvent):
if event.button() == Qt.LeftButton:
self.start_drag_pos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QtGui.QMouseEvent):
super().mouseMoveEvent(event)
if not event.buttons() == Qt.LeftButton:
return
if (event.pos() - self.start_drag_pos).manhattanLength() < QApplication.startDragDistance():
return
index = self.indexAt(self.start_drag_pos)
item = self.model.itemFromIndex(index)
if item:
drag = QDrag(self)
mime_data = QMimeData()
mime_data.setText(str(item.get_data()))
drag.setMimeData(mime_data)
drag.exec_(Qt.MoveAction)
def dragEnterEvent(self, event: QDragEnterEvent):
self.selectionModel().clear()
if event.mimeData().hasText():
mime_text = event.mimeData().text()
if event.source() == self:
mime_tuple = eval(mime_text)
if [...]:
event.acceptProposedAction()
else:
path = Path(mime_text)
accepted_extensions = ['.txt']
if path.suffix in accepted_extensions:
event.acceptProposedAction()
def dragMoveEvent(self, event: QDragMoveEvent):
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
item = self.model.itemFromIndex(index)
if self.hover_item is not item:
self.hover_item = item
if self.hover_item is not None:
self.selectionModel().clear()
self.selectionModel().select(item.index(), QItemSelectionModel.Rows | QItemSelectionModel.Select)
super().dragMoveEvent(event)
if event.source() == self:
item_data = eval(event.mimeData().text())
if item is not None:
if [...]:
event.acceptProposedAction()
else:
if [...]:
event.acceptProposedAction()
else:
path = event.mimeData().text().replace('file:///', '')
if item is not None:
if [...]:
if [...] in item.text():
event.acceptProposedAction()
else:
if [...]:
if [...] in item.value:
event.acceptProposedAction()
else:
if [...] in item.value:
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
self.hover_item = None
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
over_dropped_item = self.model.itemFromIndex(index)
dropped = event.mimeData().text().replace('file:///', '')
print('Moved item: ', dropped)
print('Moved over: ', over_dropped_item.get_data())
[...]
event.acceptProposedAction()
经过深思熟虑,我决定不覆盖 mouseMoveEvent()
方法,这解决了问题。
因为我认为 mouseMoveEvent()
和 dragMoveEvent()
之间存在冲突。事实上 dragMoveEvent()
只被调用了一次(它应该在拖动过程中被调用多次)并且就在 dropEvent()
之前。我认为问题是由于 QDrag
对象是在鼠标移动期间连续创建的,因此 dragMoveEvent()
直到项目被删除才被调用 - 但为时已晚。
没有自定义 QDrop
对象 - 以前在 mouseMoveEvent()
中创建 - 我不得不找到另一种方法来保存拖动的 QStandardItem
并在以后使用它。解决方案非常简单:在调用 dragEnterEvent()
时将 selected 项目存储到 class 属性中:
def dragEnterEvent(self, event: QDragEnterEvent):
if event.source() == self:
self.dragged_index = self.selectionModel().selectedIndexes()[0]
self.dragged_item = self.model.itemFromIndex(self.dragged_index)
另外,多亏了本机拖放,我不再需要 select 我悬停的项目,因为此功能是 built-in 并且行之间的黑线也是。本机拖放仅适用于内部移动且仅适用于 QStandardItem
项,因此我删除了 MyStandardItem
class 并使用 QStandardItem
代替。
代码
class MyTreeView(QTreeView):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.hover_item = None
self.setMouseTracking(True)
self.dragged_item = None
self.dragged_index = None
def dragEnterEvent(self, event: QDragEnterEvent):
if event.source() == self:
self.dragged_index = self.selectionModel().selectedIndexes()[0]
self.dragged_item = self.model.itemFromIndex(self.dragged_index)
if [...] not in self.dragged_item.text():
super().dragEnterEvent(event)
else:
[...]
def dragMoveEvent(self, event: QDragMoveEvent):
super().dragMoveEvent(event)
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
item = self.model.itemFromIndex(index)
if event.source() == self:
if item is not None:
if [...] in self.dragged_item.data()[0]:
if [...] in item.data()[0]:
event.acceptProposedAction()
else:
if [...] in item.data()[0]:
event.acceptProposedAction()
else:
[...]
def dropEvent(self, event: QDropEvent):
super().dropEvent(event)
[...]
我正在使用 PySide2 构建一个相当复杂的 GUI,我必须为 QTreeView 小部件实现拖放系统(内部和外部移动都应被接受)。
目标
能够将项目从 QTreeView
小部件(文件资源管理器)复制到另一个 QTreeView
小部件(测试小部件)并在此测试小部件中移动项目。应检查每个拖动的文件,以使用户了解他是否可以移动该文件以及应该将其放在哪里。
此外,当带有拖动元素的鼠标悬停时,测试小部件的项目应突出显示(一个接一个)。 (实际上,我想在这些项目之间画一条线,但我现在做不到:欢迎任何建议)。
问题
外部移动完美运行,而内部移动无法正常运行:在拖放操作期间,即使应该接受移动,'stop' 图标也会始终显示。它实际上被接受了,因为 drop 操作是成功的。
悬停项目的突出显示也不起作用。
我认为问题是由覆盖测试小部件的 mouseMoveEvent()
方法引起的,我被迫实施该方法以设置 QMimeData
对象。
代码
请注意,出于隐私原因,部分代码已替换为“[...]”。无论如何,这些部分对于系统的运行并不重要。
class MyStandardItem(QStandardItem):
def __init__(self, text, icon_path='', value='', num=0, font_size=8, set_bold=False):
super().__init__()
self.setDragEnabled(True)
self.setDropEnabled(True)
self.num = num
self.value = value
self.setText(text)
font = QFont('Segoe UI', font_size)
font.setBold(set_bold)
self.setFont(font)
self.setIcon(QIcon(icon_path))
def setCheckState(self, checkState):
super().setCheckState(checkState)
if checkState == Qt.Unchecked:
self.setForeground(QColor(150, 150, 150))
def get_data(self):
return self.text(), self.value, self.num
class MyTreeView(QTreeView):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.hover_item = None
self.setMouseTracking(True)
self.start_drag_pos = None
self.model = QStandardItemModel()
self.root = self.model.invisibleRootItem()
self.setModel(self.model)
def mousePressEvent(self, event: QtGui.QMouseEvent):
if event.button() == Qt.LeftButton:
self.start_drag_pos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QtGui.QMouseEvent):
super().mouseMoveEvent(event)
if not event.buttons() == Qt.LeftButton:
return
if (event.pos() - self.start_drag_pos).manhattanLength() < QApplication.startDragDistance():
return
index = self.indexAt(self.start_drag_pos)
item = self.model.itemFromIndex(index)
if item:
drag = QDrag(self)
mime_data = QMimeData()
mime_data.setText(str(item.get_data()))
drag.setMimeData(mime_data)
drag.exec_(Qt.MoveAction)
def dragEnterEvent(self, event: QDragEnterEvent):
self.selectionModel().clear()
if event.mimeData().hasText():
mime_text = event.mimeData().text()
if event.source() == self:
mime_tuple = eval(mime_text)
if [...]:
event.acceptProposedAction()
else:
path = Path(mime_text)
accepted_extensions = ['.txt']
if path.suffix in accepted_extensions:
event.acceptProposedAction()
def dragMoveEvent(self, event: QDragMoveEvent):
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
item = self.model.itemFromIndex(index)
if self.hover_item is not item:
self.hover_item = item
if self.hover_item is not None:
self.selectionModel().clear()
self.selectionModel().select(item.index(), QItemSelectionModel.Rows | QItemSelectionModel.Select)
super().dragMoveEvent(event)
if event.source() == self:
item_data = eval(event.mimeData().text())
if item is not None:
if [...]:
event.acceptProposedAction()
else:
if [...]:
event.acceptProposedAction()
else:
path = event.mimeData().text().replace('file:///', '')
if item is not None:
if [...]:
if [...] in item.text():
event.acceptProposedAction()
else:
if [...]:
if [...] in item.value:
event.acceptProposedAction()
else:
if [...] in item.value:
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
self.hover_item = None
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
over_dropped_item = self.model.itemFromIndex(index)
dropped = event.mimeData().text().replace('file:///', '')
print('Moved item: ', dropped)
print('Moved over: ', over_dropped_item.get_data())
[...]
event.acceptProposedAction()
经过深思熟虑,我决定不覆盖 mouseMoveEvent()
方法,这解决了问题。
因为我认为 mouseMoveEvent()
和 dragMoveEvent()
之间存在冲突。事实上 dragMoveEvent()
只被调用了一次(它应该在拖动过程中被调用多次)并且就在 dropEvent()
之前。我认为问题是由于 QDrag
对象是在鼠标移动期间连续创建的,因此 dragMoveEvent()
直到项目被删除才被调用 - 但为时已晚。
没有自定义 QDrop
对象 - 以前在 mouseMoveEvent()
中创建 - 我不得不找到另一种方法来保存拖动的 QStandardItem
并在以后使用它。解决方案非常简单:在调用 dragEnterEvent()
时将 selected 项目存储到 class 属性中:
def dragEnterEvent(self, event: QDragEnterEvent):
if event.source() == self:
self.dragged_index = self.selectionModel().selectedIndexes()[0]
self.dragged_item = self.model.itemFromIndex(self.dragged_index)
另外,多亏了本机拖放,我不再需要 select 我悬停的项目,因为此功能是 built-in 并且行之间的黑线也是。本机拖放仅适用于内部移动且仅适用于 QStandardItem
项,因此我删除了 MyStandardItem
class 并使用 QStandardItem
代替。
代码
class MyTreeView(QTreeView):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.viewport().setAcceptDrops(True)
self.hover_item = None
self.setMouseTracking(True)
self.dragged_item = None
self.dragged_index = None
def dragEnterEvent(self, event: QDragEnterEvent):
if event.source() == self:
self.dragged_index = self.selectionModel().selectedIndexes()[0]
self.dragged_item = self.model.itemFromIndex(self.dragged_index)
if [...] not in self.dragged_item.text():
super().dragEnterEvent(event)
else:
[...]
def dragMoveEvent(self, event: QDragMoveEvent):
super().dragMoveEvent(event)
cursor_pos = self.viewport().mapFromGlobal(QtGui.QCursor().pos())
index = self.indexAt(cursor_pos)
item = self.model.itemFromIndex(index)
if event.source() == self:
if item is not None:
if [...] in self.dragged_item.data()[0]:
if [...] in item.data()[0]:
event.acceptProposedAction()
else:
if [...] in item.data()[0]:
event.acceptProposedAction()
else:
[...]
def dropEvent(self, event: QDropEvent):
super().dropEvent(event)
[...]