pythondrag-and-droppyside2qtreeviewqstandarditemmodel

Qt Drag & Drop doesn't work fine with custom items


I'm building a GUI using PySide2 (Qt5) with a custom treeview widget (MyTreeView, inherited from QTreeView). The model is a QStandardItemModel object whereas the items are custom: MyStandardItem, inherited from QStandardItem.

The problem is: if I check the type of the moved item after a drag and drop action, it has become a QStandardItem but it should have been a MyStandardItem. I believe that the problem is the MimeType, and after a lot of research I found out that the solution could be creating a custom model and overriding MIME related functions. I tried to figure out how but I couldn't.

So, here are the questions:

For what it's worth, here is MyStandardItem implementation:

class MyStandardItem(QStandardItem):
    def __init__(self, text, font, icon_path='', value='', num=0, check_state=None):
        super().__init__()
        self.setDragEnabled(True)
        self.setDropEnabled(True)
        self.setText(text)
        self.setData({'value': (value, num)})
        self.setToolTip(str(self.data()['value']))
        self.setFont(font)
        self.setIcon(QIcon(icon_path))
        self.toggled = check_state
        if check_state is not None:
            self.setCheckable(True)
            self.setCheckState(check_state)

    def setCheckState(self, checkState):
        super().setCheckState(checkState)
        if checkState == Qt.Unchecked:
            self.toggled = Qt.Unchecked
        else:
            self.toggled = Qt.Checked

Solution

  • I found a way to solve this problem without having to create a custom model.

    In the MyTreeView.dropEvent function: I dont't call super().dropEvent() to complete the drag&drop action but I implement it by myself by copying item's data in a variable and creating a new MyStandardItem from those data. Then I call insertRow() to insert the new item in the given position and deleteRow() to delete the old item. Clearly, the moved item has to be stored in a class attribute at the beginning of the action (DragEnterEvent()). Everything works perfectly.

    PS: I've already tried this way before but I always ended up by having an empty row. The difference here is that I create a new item instead of re-inserting the old one.

    Here's some parts of my code to clarify what I mean. Please, note that these functions are MyTreeView's methods.

    def dragEnterEvent(self, event: QDragEnterEvent):
        if event.source() == self:
            self.dragged_item = self.model.itemFromIndex(self.selectionModel().selectedIndexes()[0])
            super().dragEnterEvent(event)
        else:
            ...
    
    
    def dropEvent(self, event: QDropEvent):
        index = self.indexAt(event.pos())
        if not index.isValid():
            return
        over_item = self.model.itemFromIndex(index)
        over_value = over_item.data()['value'][0]
        if event.source() == self:
            item_was_moved = self._move_item(over_value, over_parent_value)
            if not item_was_moved:
                return
        else:
            ...
    
    
    def _move_item(self, over_value, over_parent_value):
        over_value = self._check_indicator_position(over_value)
        if over_value is None:
            return False
        dragged_parent_value = self.dragged_item.parent().data()['value'][0]
        dragged_value = self.dragged_item.data()['value'][0]
        row = self.dragged_item.row()
        items = guifunc.copy_row_items(self.dragged_item, row)
        over_value_num = int(over_value.strip('test'))
        self.dragged_item.parent().insertRow(over_value_num, items)
        if over_value_num < row:
            row += 1
        self.dragged_item.parent().removeRow(row)
        return True