0
votes

I am trying to do drag and drop of rows inside a QTableWidget.

The drag and drop operation itself is working great, but when I click and release the mouse button rapidly on a row , the drop event is never called. Even with the mouse released, the cursor is still in drag mode until I click again.

When I click an release slowly on a row, to select it, the events are: - mousePressEvent - dragEnterEvent - dropEvent

When I click and release rapidly, the events are: - mousePressEvent - mouseReleaseEvent - dragEnterEvent and dropEvent is only called if I click again.

This is probably because the dragEnterEvent (that should never be called, I think) 'hides' the mouseReleaseEvent to the drag operation.

Is there a way to force the end of the drag operation ? or better, to prevent the drag operation to call dragEnterEvent ?

Here is my code:

class DragDropTableWidget(QTableWidget):

    moveRow = pyqtSignal(int, int)

    def __init__(self, parent):
        super(QTableWidget, self).__init__(parent)

        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.viewport().setAcceptDrops(True)
        self.setDragDropOverwriteMode(False)
        self.setDropIndicatorShown(True)
        self.setSelectionMode(QAbstractItemView.SingleSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setDragDropMode(QAbstractItemView.InternalMove)

        self.released = False

    def mousePressEvent(self, event):
        print('Mouse Press')
        self.released = False

        index = self.indexAt(event.pos())
        if not index.isValid():
            return

        row = index.row()
        self.selectRow(row)

        mime_data = QMimeData()
        mime_data.setData("index", str(row))

        drag = QDrag(self)
        drag.setMimeData(mime_data)
        drag.start(Qt.MoveAction)

    def dragEnterEvent(self, e):
        print('Drag Enter')
        if self.released:
            # todo: cancel drag and drop
            pass
        else:
            e.accept()

    def dragMoveEvent(self, e):
        e.accept()

    def mouseReleaseEvent(self, e):
        print('Mouse release')
        self.released = True

    def dropEvent(self, event):
        print('Drop event')
        if event.source() == self:
            index = self.indexAt(event.pos())
            if not index.isValid():
                event.accept()
                return
            start_index = int(event.mimeData().data("index"))
            if start_index != index.row():
                print ("dropEvent called from row {} on row {}".format(start_index, index.row()))
                self.moveRow.emit(start_index, index.row())

            event.accept()

Thanks a lot

1
I cannot reproduce your issue, as I always "correctly" get the drop event even with "rapid" clicking (tested with PyQt4 on Linux and Wine, though, so it's possible that the native Windows implementation reacts differently). In any case, starting a drag operation on mouse click is not suggested (usually the mouseMoveEvent is preferred), and getting an immediate dragEnterEvent is the expected behavior.musicamante

1 Answers

0
votes

Thank you musicamante for showing me the right direction. This is a working implementation for a QTableWidget with drag and drop support of table rows:

class DragDropTableWidget(QTableWidget):
# signal sent on drag and drop end operation
moveRow = pyqtSignal(int, int)

def __init__(self, parent):
    super(QTableWidget, self).__init__(parent)

    self.setDragEnabled(True)
    self.setAcceptDrops(True)
    self.viewport().setAcceptDrops(True)
    self.setDragDropOverwriteMode(False)
    self.setDropIndicatorShown(True)
    self.setSelectionMode(QAbstractItemView.SingleSelection)
    self.setSelectionBehavior(QAbstractItemView.SelectRows)
    self.setDragDropMode(QAbstractItemView.InternalMove)

def mouseMoveEvent(self, event):
    if event.type() == QEvent.MouseMove and event.buttons() & Qt.LeftButton:
        index = self.indexAt(event.pos())
        if not index.isValid():
            return

        mime_data = QMimeData()
        mime_data.setData("index", str(index.row()))

        drag = QDrag(self)
        drag.setMimeData(mime_data)

        drag.start(Qt.MoveAction)

def dragEnterEvent(self, e):
    e.accept()

def dragMoveEvent(self, e):
    e.accept()

def dropEvent(self, event):
    if event.source() == self:
        index = self.indexAt(event.pos())
        if not index.isValid():
            event.accept()
            return
        start_index = int(event.mimeData().data("index"))
        if start_index != index.row():
            print ("dropEvent called from row {} on row {}".format(start_index, index.row()))
            self.moveRow.emit(start_index, index.row())

        event.accept()