如何从一个QListWidget拖放到另一个QListWidget
在一个对话框窗口里,有两个QListWidget。两个列表都开启了拖放功能。如果我把一个文件拖到任意一个列表上,程序能识别到这个操作,并打印出被拖放的文件列表。不过,除了拖放文件之外,我还想实现从一个列表拖放项目到另一个列表的功能。当我拖动列表中的项目时,拖放事件会被触发,但程序却无法识别出哪些项目被拖放到了目标列表上。下面是示例代码。我的目标是将列表项目从一个QListWidget拖放到另一个。
import sys, os
from PyQt4 import QtCore, QtGui
class ThumbListWidget(QtGui.QListWidget):
def __init__(self, type, parent=None):
super(ThumbListWidget, self).__init__(parent)
self.setAcceptDrops(True)
self.setIconSize(QtCore.QSize(124, 124))
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(QtCore.SIGNAL("dropped"), links)
else:
event.ignore()
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
self.listItems={}
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.listWidgetA = ThumbListWidget(self)
for i in range(12):
QtGui.QListWidgetItem( 'Item '+str(i), self.listWidgetA )
myBoxLayout.addWidget(self.listWidgetA)
self.listWidgetB = ThumbListWidget(self)
myBoxLayout.addWidget(self.listWidgetB)
self.listWidgetA.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.listWidgetA.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.connect(self.listWidgetA, QtCore.SIGNAL("dropped"), self.items_dropped)
self.listWidgetA.currentItemChanged.connect(self.item_clicked)
self.listWidgetB.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.listWidgetB.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.connect(self.listWidgetB, QtCore.SIGNAL("dropped"), self.items_dropped)
self.listWidgetB.currentItemChanged.connect(self.item_clicked)
def items_dropped(self, arg):
print arg
def item_clicked(self, arg):
print arg
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
编辑 #2
这里是实现所有功能的代码。但是没有办法追踪到到底哪个对象被拖放了。droppedOnA()和droppedOnB()这两个方法仍然无法正常工作。
from PyQt4 import QtGui, QtCore
import sys, os
class MyClassItem(QtGui.QListWidgetItem):
def __init__(self, parent=None):
super(QtGui.QListWidgetItem, self).__init__(parent)
class ThumbListWidget(QtGui.QListWidget):
def __init__(self, type, parent=None):
super(ThumbListWidget, self).__init__(parent)
self.setIconSize(QtCore.QSize(124, 124))
self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
super(ThumbListWidget, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
super(ThumbListWidget, self).dragMoveEvent(event)
def dropEvent(self, event):
print 'dropEvent', event
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(QtCore.SIGNAL("dropped"), links)
else:
event.setDropAction(QtCore.Qt.MoveAction)
super(ThumbListWidget, self).dropEvent(event)
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
self.listItems={}
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.listWidgetA = ThumbListWidget(self)
self.listWidgetB = ThumbListWidget(self)
for i in range(7):
listItemAInstance=MyClassItem()
listItemAInstance.setText('A'+'%04d'%i)
listItemAInstance.setBackgroundColor(QtCore.Qt.darkGray)
if i%2: listItemAInstance.setBackgroundColor(QtCore.Qt.gray)
self.listWidgetA.addItem(listItemAInstance)
listItemBInstance=MyClassItem()
listItemBInstance.setText('B'+'%04d'%i)
if i%2: listItemBInstance.setBackgroundColor(QtCore.Qt.lightGray)
self.listWidgetB.addItem(listItemBInstance)
myBoxLayout.addWidget(self.listWidgetA)
myBoxLayout.addWidget(self.listWidgetB)
self.connect(self.listWidgetA, QtCore.SIGNAL("dropped"), self.droppedOnA)
self.connect(self.listWidgetB, QtCore.SIGNAL("dropped"), self.droppedOnB)
def droppedOnA(self, arg):
print '\n\t droppedOnA', arg.text
def droppedOnB(self, arg):
print '\n\t droppedOnB', arg.text
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
编辑 #3
这是另一个尝试,这次使用MIME来传递被拖放项目的对象到QListWidget。不幸的是,cPickle拒绝接受二进制对象,并抛出了一个错误:
TypeError: the sip.wrapper type cannot be instantiated or sub-classed
为了绕过这个问题,我把每个对象的名称转换成字符串,并用它作为self.listItems={}字典的键来获取列表项目的二进制对象。这似乎运行得不错。但在我几乎以为一切都完成的时候,一个没有明显错误的QListWidget却没有把被拖放的列表项目添加到自己里面……这很奇怪。
self.listWidgetB.addItem(droppedItemInstance)
.
from PyQt4 import QtGui, QtCore
import sys, os
import cPickle
class MyClassItem(QtGui.QListWidgetItem):
def __init__(self, parent=None):
super(QtGui.QListWidgetItem, self).__init__(parent)
class ThumbListWidget(QtGui.QListWidget):
def __init__(self, type, parent=None):
super(ThumbListWidget, self).__init__(parent)
self.setIconSize(QtCore.QSize(124, 124))
self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
super(ThumbListWidget, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
super(ThumbListWidget, self).dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
else:
event.setDropAction(QtCore.Qt.MoveAction)
super(ThumbListWidget, self).dropEvent(event)
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, droppedItems):
mimedata = QtCore.QMimeData()
droppedItemsAsStrings=[]
for each in droppedItems:
droppedItemsAsStrings.append( str(each) )
bstream = cPickle.dumps(droppedItemsAsStrings)
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, action, mimedata, row):
if action == QtCore.Qt.IgnoreAction: return True
dropped=cPickle.loads(str(mimedata.data('bstream')))
self.emit(QtCore.SIGNAL("dropped"), dropped)
return True
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
self.listItems={}
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.listWidgetA = ThumbListWidget(self)
self.listWidgetB = ThumbListWidget(self)
for i in range(7):
listItemAInstance=MyClassItem()
listItemAInstance.setText('A'+'%04d'%i)
listItemAInstance.setBackgroundColor(QtCore.Qt.darkGray)
if i%2: listItemAInstance.setBackgroundColor(QtCore.Qt.gray)
self.listWidgetA.addItem(listItemAInstance)
listItemBInstance=MyClassItem()
listItemBInstance.setText('B'+'%04d'%i)
if i%2: listItemBInstance.setBackgroundColor(QtCore.Qt.lightGray)
self.listWidgetB.addItem(listItemBInstance)
self.listItems[str(listItemAInstance)]=listItemAInstance
self.listItems[str(listItemBInstance)]=listItemBInstance
myBoxLayout.addWidget(self.listWidgetA)
myBoxLayout.addWidget(self.listWidgetB)
self.connect(self.listWidgetA, QtCore.SIGNAL("dropped"), self.droppedOnA)
self.connect(self.listWidgetB, QtCore.SIGNAL("dropped"), self.droppedOnB)
def droppedOnA(self, droppedItemsAsStrings):
print '\n\t droppedOnA()'
for each in droppedItemsAsStrings:
if each in self.listItems.keys():
droppedItemInstance = self.listItems[each]
print 'adding', droppedItemInstance.text()
self.listWidgetA.addItem(droppedItemInstance)
def droppedOnB(self, droppedItemsAsStrings):
print '\n\t droppedOnB()'
for each in droppedItemsAsStrings:
if each in self.listItems.keys():
droppedItemInstance = self.listItems[each]
self.listWidgetB.addItem(droppedItemInstance)
print 'adding', droppedItemInstance.text()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
3 个回答
上面的代码对我来说没用,因为我在用Python 3的super()
时遇到了问题。
问题在于,子类里定义的dragMoveEvent
和dragDropEvent
方法没有覆盖掉父类里的旧定义。
所以event.setDropAction(QtCore.Qt.MoveAction)
这个调用没有执行,导致小部件的默认行为变成了QtCore.Qt.CopyAction
。
我通过使用从QAbstractItemView
类继承来的setDefaultDropAction()
方法解决了这个问题。
下面是PyQt5和Python 3.7的实现代码:
class NewDragDropWidget(QListWidget):
def __init__(self):
super().__init__()
self.setIconSize(QtCore.QSize(124, 124))
self.setDragDropMode(QAbstractItemView.DragDrop)
self.setDefaultDropAction(QtCore.Qt.MoveAction) # this was the magic line
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setAcceptDrops(True)
假设你想要移动列表小部件中的项目,你的子类应该像下面这样(注意,setDragDropMode
和setSelectionMode
已经被放到了__init__
里面):
class ThumbListWidget(QtGui.QListWidget):
def __init__(self, type, parent=None):
super(ThumbListWidget, self).__init__(parent)
self.setIconSize(QtCore.QSize(124, 124))
self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
super(ThumbListWidget, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
super(ThumbListWidget, self).dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(QtCore.SIGNAL("dropped"), links)
else:
event.setDropAction(QtCore.Qt.MoveAction)
super(ThumbListWidget, self).dropEvent(event)
这是一个修改过的代码。运行得非常顺利!太棒了!
from PyQt4 import QtGui, QtCore
import sys, os
class ThumbListWidget(QtGui.QListWidget):
def __init__(self, type, parent=None):
super(ThumbListWidget, self).__init__(parent)
self.setIconSize(QtCore.QSize(124, 124))
self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
super(ThumbListWidget, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
super(ThumbListWidget, self).dragMoveEvent(event)
def dropEvent(self, event):
print 'dropEvent', event
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(QtCore.SIGNAL("dropped"), links)
else:
event.setDropAction(QtCore.Qt.MoveAction)
super(ThumbListWidget, self).dropEvent(event)
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
self.listItems={}
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.listWidgetA = ThumbListWidget(self)
for i in range(12):
QtGui.QListWidgetItem( 'Item '+str(i), self.listWidgetA )
myBoxLayout.addWidget(self.listWidgetA)
self.listWidgetB = ThumbListWidget(self)
myBoxLayout.addWidget(self.listWidgetB)
self.connect(self.listWidgetA, QtCore.SIGNAL("dropped"), self.items_dropped)
self.listWidgetA.currentItemChanged.connect(self.item_clicked)
self.connect(self.listWidgetB, QtCore.SIGNAL("dropped"), self.items_dropped)
self.listWidgetB.currentItemChanged.connect(self.item_clicked)
def items_dropped(self, arg):
print 'items_dropped', arg
def item_clicked(self, arg):
print arg
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())