I am trying to get drag&drop to work between two QListViews using a custom QStandardItem. I can't find the info I need online other than this document which helped a little bit but now I'm stuck.
Drag&drop from one QListView to another works fine when I use a QStandardItem to hold my data, but when I use a custom item I run into trouble, because the receiving model/view creates a QStandardItem when a custom item is dropped.
Ideally I could tell the receiving model to use my custom item as the default item and otherwise just do it's thing, but I suppose it won't be that easy?! It seems that everything works out of the box except the creation of the QStandardItem upon drop, rather than my custom item, so I am hoping I don't have to re-invent the (drag&drop) wheel just to get that one part right?!
If I do have to re-invent the wheel and implement the view's dropEvent to then manually append the incoming items, I am running into another oddity. Here is my test code (including some code for decoding the dropped data that I found online):
from PySide import QtCore, QtGui
class MyItem(QtGui.QStandardItem):
'''This is the item I'd like to drop into the view'''
def __init__(self, parent=None):
super(MyItem, self).__init__(parent)
self.testAttr = 'test attribute value'
class ReceivingView(QtGui.QListView):
'''Custom view to show the problem - i.e. the dropEvent produces a QStandardItem rather than MyItem'''
def __init__(self, parent=None):
super(ReceivingView, self).__init__(parent)
def decode_data(self, bytearray):
'''Decode byte array to receive item back'''
data = []
item = {}
ds = QtCore.QDataStream(bytearray)
while not ds.atEnd():
row = ds.readInt32()
column = ds.readInt32()
map_items = ds.readInt32()
for i in range(map_items):
key = ds.readInt32()
value = MyItem()
ds >> value
#item[QtCore.Qt.ItemDataRole(key)] = value
item = value
data.append(item)
return data
def dropEvent(self, event):
byteArray = event.mimeData().data('application/x-qabstractitemmodeldatalist')
for item in self.decode_data(byteArray):
copiedItem = MyItem(item)
newItem = MyItem('hello')
print copiedItem
print newItem
self.model().appendRow(copiedItem) # the copied item does not show up, even though it is appended to the model
#self.model().appendRow(newItem) # this works as expected
event.accept()
item = self.model().item(self.model().rowCount() - 1)
print item
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mw = QtGui.QMainWindow()
w = QtGui.QSplitter()
mw.setCentralWidget(w)
# models
model1 = QtGui.QStandardItemModel()
model2 = QtGui.QStandardItemModel()
for i in xrange(5):
#item = QtGui.QStandardItem()
item = MyItem()
item.setData(str(i), QtCore.Qt.DisplayRole)
model1.appendRow(item)
# views
view1 = QtGui.QListView()
view2 = ReceivingView()
for v in (view1, view2):
v.setViewMode(QtGui.QListView.IconMode)
view1.setModel(model1)
view2.setModel(model2)
w.addWidget(view1)
w.addWidget(view2)
mw.show()
mw.raise_()
sys.exit(app.exec_())
The idea is to decode the dropped data to receive the original item back, then make a copy and append that copy to the receiving model. The custom item is appended to the model, but it does not show up in the view after the drop event. If I create a new custom item inside the drop even and append that, everything works as expected.
So I got two questions regarding the above:
Thanks in advance, frank
It looks like you want setItemPrototype. This provides an item factory for the model, so that it will implicitly use your custom class whenever necessary.
All you need to do is reimplement clone()
in your item class:
class MyItem(QtGui.QStandardItem):
'''This is the item I'd like to drop into the view'''
def __init__(self, parent=None):
super(MyItem, self).__init__(parent)
self.testAttr = 'test attribute value'
def clone(self):
return MyItem()
An then set an instance of that class as the prototype on the receiving model:
# models
model1 = QtGui.QStandardItemModel()
model2 = QtGui.QStandardItemModel()
model2.setItemPrototype(MyItem())
You can forget about all the datastream stuff.
PS:
I suppose I should point out that Qt obviously knows nothing about any python data attributes that may have been set during the item's lifetime, and so those won't get serialized when the item is transferred during a drag and drop operation. If you want to persist data like that, use setData()
with a custom role:
class MyItem(QtGui.QStandardItem):
_TestAttrRole = QtCore.Qt.UserRole + 2
def clone(self):
item = MyItem()
item.testArr = 'test attribute value'
return item
@property
def testAttr(self):
return self.data(self._TestAttrRole)
@testAttr.setter
def testAttr(self, value):
self.setData(value, self._TestAttrRole)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With