从QListWidget拖动多个项目时,将删除不可拖动的项目

时间:2017-04-07 16:43:29

标签: qt pyside

我有两个QListWidgets。用户可以从一个列表中选择多个项目并将它们拖到另一个列表中。但是在每个列表中,有些项目是可拖动的,有些则不是。如果选择包含可拖动项和不可拖动项,则会出现问题。只有可拖动的项目出现在第二个列表中,这是正确的。但所有项目从第一个列表中消失。 animation of items being dragged from one list to another

在上面的动画图像中,选择了项目00,01和02。仅启用项目00和02。拖放后,所有三个项目都从第一个列表中消失。我该如何解决这个问题?

以下是一些重现问题的代码:

import random
import sys
from PySide import QtCore, QtGui

class TestMultiDragDrop(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(TestMultiDragDrop, self).__init__(parent)

        centralWidget = QtGui.QWidget()
        self.setCentralWidget(centralWidget)

        layout = QtGui.QHBoxLayout(centralWidget)
        self.list1 = QtGui.QListWidget()
        self.list1.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
        self.list1.setDefaultDropAction(QtCore.Qt.MoveAction)
        self.list1.setSelectionMode(QtGui.QListWidget.ExtendedSelection)

        self.list2 = QtGui.QListWidget()
        self.list2.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
        self.list2.setDefaultDropAction(QtCore.Qt.MoveAction)
        self.list2.setSelectionMode(QtGui.QListWidget.ExtendedSelection)

        layout.addWidget(self.list1)
        layout.addWidget(self.list2)

        self.fillListWidget(self.list1, 8, 'someItem')
        self.fillListWidget(self.list2, 4, 'anotherItem')

    def fillListWidget(self, listWidget, numItems, txt):
        for i in range(numItems):
            item = QtGui.QListWidgetItem()
            newTxt = '{0}{1:02d}'.format(txt, i)
            if random.randint(0, 1):
                item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
            else:
                # If the item is draggable, indicate it with a *
                newTxt += ' *'
                item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsDragEnabled)
            item.setText(newTxt)
            listWidget.addItem(item)

def openMultiDragDrop():
    global multiDragDropUI
    try:
        multiDragDropUI.close()
    except:
        pass
    multiDragDropUI = TestMultiDragDrop()
    multiDragDropUI.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    multiDragDropUI.show()
    return multiDragDropUI

if __name__ == '__main__':
    app = QtGui.QApplication([])
    openMultiDragDrop()
    sys.exit(app.exec_())

2 个答案:

答案 0 :(得分:0)

我怀疑setDefaultDropAction(QtCore.Qt.MoveAction)

请阅读以下文档中的段落:特别是粗线

在最简单的情况下,拖放操作的目标会收到正在拖动的数据的副本,来源会决定是否删除原版的。这由CopyAction操作描述。目标还可以选择处理其他操作,特别是MoveAction和LinkAction操作。 如果源调用QDrag :: exec()并返回MoveAction,则源负责删除任何原始数据(如果它选择这样做)。不应删除源小部件创建的QMimeData和QDrag对象 - 它们将被Qt 销毁。

http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions

首先尝试使用QtCore.Qt.CopyAction

其次,如果MoveAction是强制性的,请尝试在源列表窗口小部件的QMimeData中创建QDragmouseMoveEvent个对象。

在下面的链接中,您可以找到一些有关在源列表窗口小部件QMimeData中创建QDragmouseMoveEvent对象的帮助。 (代码用C ++编写,我的目的是获得概念性的想法)。

http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions

答案 1 :(得分:0)

我认为Kuba Ober是对的,这是一个Qt错误。在C ++源代码中,有一个函数void QAbstractItemViewPrivate::clearOrRemove()。它会删除所有选定的行,但不会查看是否启用了每个项目。

在这种情况下,我提出了一些解决方法:

方法1:使所有不可拖动的项目也不可选择

这是最简单的方法。只需从所有不可拖动的项目中删除QtCore.Qt.ItemIsEnabled标志即可。当然,如果您希望所有项目都可以选择,那么这将不起作用。

方法2:重新创建“startDrag”函数

由于clearOrRemove函数属于私有类,因此我无法覆盖它。但是该函数由startDrag函数调用,可以覆盖该函数。所以我基本上复制了Python中的函数,并用我自己的函数clearOrRemove替换了removeSelectedDraggableItems的调用。

此方法的问题是startDrag包含对属于私有类的一些其他函数的调用。这些函数调用其他私有类函数。具体而言,这些函数负责控制在拖动事件期间如何绘制项目。由于我不想重新创建所有函数,我只是忽略了这些。结果是这种方法产生了正确的功能,但它失去了拖动哪些项目的图形指示。

class DragListWidget(QtGui.QListWidget):
    def __init__(self):
        super(DragListWidget, self).__init__()

    def startDrag(self, supportedDragActions):
        indexes = self.getSelectedDraggableIndexes()
        if not indexes:
            return

        mimeData = self.model().mimeData(indexes)
        if not mimeData:
            return

        drag = QtGui.QDrag(self)
        rect = QtCore.QRect()
        # "renderToPixmap" is from a private class in the C++ code, so I can't use it.
        #pixmap = renderToPixmap(indexes, rect)
        #drag.setPixmap(pixmap)
        drag.setMimeData(mimeData)
        # "pressedPosition" is from a private class in the C++ code, so I can't use it.
        #drag.setHotSpot(pressedPostion() - rect.topLeft())
        defaultDropAction = self.defaultDropAction()
        dropAction = QtCore.Qt.IgnoreAction
        if ((defaultDropAction != QtCore.Qt.IgnoreAction) and 
            (supportedDragActions & defaultDropAction)):
            dropAction = defaultDropAction
        elif ((supportedDragActions & QtCore.Qt.CopyAction) and
              (self.dragDropMode() != self.InternalMove)):
            dropAction = QtCore.Qt.CopyAction

        dragResult = drag.exec_(supportedDragActions, dropAction)
        if dragResult == QtCore.Qt.MoveAction:
            self.removeSelectedDraggableItems()

    def getSelectedDraggableIndexes(self):
        """ Get a list of indexes for selected items that are drag-enabled. """
        indexes = []
        for index in self.selectedIndexes():
            item = self.itemFromIndex(index)
            if item.flags() & QtCore.Qt.ItemIsDragEnabled:
                indexes.append(index)
        return indexes

    def removeSelectedDraggableItems(self):
        selectedDraggableIndexes = self.getSelectedDraggableIndexes()
        # Use persistent indices so we don't lose track of the correct rows as
        # we are deleting things.
        root = self.rootIndex()
        model = self.model()
        persistentIndices = [QtCore.QPersistentModelIndex(i) for i in selectedDraggableIndexes]
        for pIndex in persistentIndices:
            model.removeRows(pIndex.row(), 1, root)

方法3:破解“startDrag”

此方法在调用内置“startDrag”方法之前将放置操作从“MoveAction”更改为“CopyAction”。然后它调用自定义函数来删除所选的启用拖动的项目。这解决了丢失图形拖动动画的问题。

这是一个相当容易的黑客,但它有自己的问题。假设用户安装了一个事件过滤器,在某些情况下会将放置操作从“MoveAction”更改为“IgnoreAction”。此hack代码未获取更新的值。它仍将删除项目,就好像操作是“MoveAction”一样。 (方法2没有这个问题。)这个问题有解决方法,但我不会在这里讨论。

class DragListWidget2(QtGui.QListWidget):
    def startDrag(self, supportedDragActions):
        dropAction = self.defaultDropAction()
        if dropAction == QtCore.Qt.MoveAction:
            self.setDefaultDropAction(QtCore.Qt.CopyAction)
        super(DragListWidget2, self).startDrag(supportedDragActions)
        if dropAction == QtCore.Qt.MoveAction:
            self.setDefaultDropAction(dropAction)
            self.removeSelectedDraggableItems()

    def removeSelectedDraggableItems(self):
        # Same code from Method 2.  Removed here for brevity.
        pass