Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to drop a custom QStandardItem into a QListView

Tags:

qt

pyqt

pyside

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:

  1. Is this approach the right one to enable the dropping of custom items or is there an easier one?
  2. Why does the copy of the custom item in the above code not show up in the view after the drop?

Thanks in advance, frank

like image 276
Frank Rueter Avatar asked Oct 19 '22 01:10

Frank Rueter


1 Answers

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)
like image 86
ekhumoro Avatar answered Oct 21 '22 04:10

ekhumoro