覆盖 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)
        [...]