Search code examples
qtpyqtdrag-and-dropqtableviewqabstractitemmodel

PyQt signal to track when row in QTableView was moved


I am using a subclassed version of QAbstractItemModel with a QTableView and have drag-and-drop activated with a subclassed model.dropMimeData(), model.insertRows(), model.removeRows(). Now I want to show the changes after a drag-and-drop operation is finished and offer the user to undo the operation again. I therefore implemented my own dropEvent()-method for the tableView. I also set the move method to InternalMove.

I check for a confirmation of the move inside the method and then call super(widget.__class__, widget).dropEvent(event). I would expect that after this execution the row was inserted at the new position and deleted at the old position. What happens is that it inserts the row at the specified position, but it deletes row at the old position only after dropEvent() is finished. It does not matter if I call event.accept() or event.acceptProposedAction() inside the function, it always waits until dropEvent() finishes.

I am looking for a signal that tells me when a drag-and-drop operation was executed. I would expect QAbstractItemModel's rowsMoved-signal to be what I want, but it is not emitted during the dnd-operation. The signals rowsInserted and rowsRemoved are however emitted. But the rowsRemoved signal is just emitted as soon as dropEvent() finishes. Does anybody know where QTableView executes the insertion of the target row, setting of the data and the removal of the source row?

I am using python3 with PyQt5 on Windows 10.

    def dropEvent_withConfirm(widget, event):
        dropInd = widget.indexAt(event.pos())
        if not dropInd.isValid():
            event.ignore()
            return

        confirmGUI = ConfirmGUI("Drag Element to new position?",
                                "Are you sure you want to drag the element to this position?",
                                True)
        if confirmGUI.getConfirmation():
            super(widget.__class__, widget).dropEvent(event)
            event.accept()
            # Here I want to do something after the finished move, but here the target row was inserted, but the source row is not yet deleted
        else:
            event.ignore()

    self.client.tblView.dropEvent = lambda e: dropEvent_withConfirm(self.client.tblView, e)

Solution

  • I solved it now by not relying on the super().dropEvent(), but by implementing it myself. I couldn't find a fitting signal that is emitted on finalization of the drop. Down below is my updated code:

    def dropEvent_withConfirm(widget, event):
        dropInd = widget.indexAt(event.pos())
        if not dropInd.isValid():
            event.ignore()
            return
    
        confirmGUI = ConfirmGUI("Drag Element to new position?",
                                "Are you sure you want to drag the element to this position?",
                                True)
        if confirmGUI.getConfirmation():
            old_row, new_row = getSrcAndDstRow()
            entry            = getDraggedEntry()
            self.myModel.insertRows(new_row)
            self.myModel.setRow(new_row, entry)
            if old_row > new_row:
                self.myModel.removeRows(old_row + 1)
            else:
                self.myModel.removeRows(old_row)
            event.accept()
            # Here I can now do something after the finished move
        else:
            event.ignore()
    
    self.client.tblView.dropEvent = lambda e: dropEvent_withConfirm(self.client.tblView, e)