覆盖mouseMoveEvent时,dragMoveEvent无法正常工作-Qt Drag&Drop

问题描述

我正在使用pyside2构建非常复杂的GUI,并且必须为QTreeView小部件实现拖放系统(内部和外部移动均应接受)。

目标

能够将项目从QTreeView小部件(文件浏览器)复制到另一个QTreeView小部件(“测试”小部件)并在此“测试”小部件中移动项目。应检查每个拖动的文件,以使用户了解他是否可以移动该文件以及应将其放置在何处。 同样,当带有拖动元素的鼠标将其悬停时,“测试”小部件的各项也应突出显示一个一个)。 (实际上,我想在项目之间划一条界线,但我暂时无法这样做:欢迎提出任何建议。)

问题

外部移动效果很好,而内部移动则无法正常工作:在拖放操作期间,即使应该接受该移动,也始终显示“停止”图标。由于放置操作成功完成,因此实际上已被接受。 悬停项目的突出显示也不起作用。 我认为问题是由Tests小部件的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()时,将所选项目存储到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)

此外,由于有了本机的拖放功能,我不再需要选择要悬停的项目,因为该功能是内置的,行之间的黑线也是如此。原生拖放仅适用于内部移动,仅适用于QStandardItem项,因此我删除了MyStandardItem类,而使用了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)
        [...]