我有两个QListWidgets。用户可以从一个列表中选择多个项目并将它们拖到另一个列表中。但是在每个列表中,有些项目是可拖动的,有些则不是。如果选择包含可拖动项和不可拖动项,则会出现问题。只有可拖动的项目出现在第二个列表中,这是正确的。但所有项目从第一个列表中消失。
在上面的动画图像中,选择了项目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_())
答案 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
中创建QDrag
和mouseMoveEvent
个对象。
在下面的链接中,您可以找到一些有关在源列表窗口小部件QMimeData
中创建QDrag
和mouseMoveEvent
对象的帮助。 (代码用C ++编写,我的目的是获得概念性的想法)。
http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions
答案 1 :(得分:0)
我认为Kuba Ober是对的,这是一个Qt错误。在C ++源代码中,有一个函数void QAbstractItemViewPrivate::clearOrRemove()
。它会删除所有选定的行,但不会查看是否启用了每个项目。
在这种情况下,我提出了一些解决方法:
这是最简单的方法。只需从所有不可拖动的项目中删除QtCore.Qt.ItemIsEnabled
标志即可。当然,如果您希望所有项目都可以选择,那么这将不起作用。
由于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)
此方法在调用内置“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