1

I know how to drag rows

    QTableView.verticalHeader().setSectionsMovable(True)
    QTableView.verticalHeader().setDragEnabled(True)
    QTableView.verticalHeader().setDragDropMode(qtw.QAbstractItemView.InternalMove)

but I want to be able to drag and drop just a single (or a pair of) cell.

Can anyone point me in the right direction?

PS: my current idea would be to intercept the clicked() -> dragEnter() -> dragLeave() or fork to dragDrop() -> dragIndicatorPosition() events, but it sounds kinda convoluted

I did read this but I am confused on how to implement it, especially the section "Enabling drag and drop for items" seems to address exactly what I need. I'll see if I can post a "slim" example.

EDIT:

here an example with some other stuff. in MyStandardItemModel I try to do the trick:

from PyQt5 import QtCore, QtWidgets, QtSql
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView, QTableWidget


class MyStandardItemModel(QStandardItemModel):
    def __init__(self, parent=None, *arg, **kwargs):
        super().__init__(parent, *arg, **kwargs)
        self.__readonly_cols = []

    def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
        try:
            default_Flags = QStandardItemModel.flags(self,index)
            if (index.column() in self.__readonly_cols):
                return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
            else:
                if (index.isValid()):
                    return default_Flags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
                else:
                    return default_Flags
        except Exception as ex:
            print(ex)


    def setReadOnly(self, columns: [int]):
        for i in columns:
            if i <= (self.columnCount() - 1) and i not in self.__readonly_cols:
                self.__readonly_cols.append(i)

    def resetReadOnly(self):
        self.__readonly_cols = []


class MyTableView(QtWidgets.QTableView):
    def __init__(self, parent=None, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)



class CheckBoxDelegate(QtWidgets.QItemDelegate):
    """
    A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
    """
    # signal to inform clicking
    user_click = QtCore.pyqtSignal(int, int, bool)

    def __init__(self, parent):
        QtWidgets.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        """
        Important, otherwise an editor is created if the user clicks in this cell.
        """
        return None

    def paint(self, painter, option, index):
        """
        Paint a checkbox without the label.
        """
        self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)

    def editorEvent(self, event, model, option, index):
        '''
        Change the data in the model and the state of the checkbox
        if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
        '''
        if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
            return False

        if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
            # Change the checkbox-state
            self.setModelData(None, model, index)
            return True

        # if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
        #     return False

        return False


    def setModelData (self, editor, model, index):
        '''
        The user wanted to change the old state in the opposite.
        '''
        try:
            if int(index.data()) == 0:
                model.setData(index, 1, QtCore.Qt.EditRole)
                ret = True
            else:
                model.setData(index, 0, QtCore.Qt.EditRole)
                ret = False
            # emit signal with row, col, and the status
            self.user_click.emit(index.row(), index.column(), ret)
        except Exception as ex:
            print(ex)


if __name__ == '__main__':

    import sys

    app = QApplication(sys.argv)
    model = MyStandardItemModel(5, 5)
    header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
    model.setHorizontalHeaderLabels(header_labels)
    tableView = MyTableView()
    tableView.setModel(model)

    delegate = CheckBoxDelegate(None)
    tableView.setItemDelegateForColumn(0, delegate)
    for row in range(5):
        for column in range(4):
            index = model.index(row, column, QModelIndex())
            model.setData(index, 0)
    model.setReadOnly([1,2])
    tableView.setWindowTitle("CheckBox, readonly and drag&drop example")
    tableView.show()
    sys.exit(app.exec_())
Caligola
  • 59
  • 9
  • That depends on what type of action you want from the drag&drop: move? copy? switch? If multiple items are selected, then how would they be arranged in the new position? Also, note that you only need `setSectionsMovable` for changing row/column header order, and that's just a *visual* arrangement of the layout (the model isn't changed, so that's not "real" drag&drop), actual internal drag&drop alters the model structure. – musicamante May 04 '21 at 12:55
  • @musicamante: right. only single selection would be allowed (missing in the example). I'd say move and/or switch I was thinking mouse button dependent: move if dragged with the left button pressed, switch with the right. – Caligola May 04 '21 at 14:18
  • @musicamante: So the model approach won't be working as I intend, is that what you say? I don't want to just rearrange the view, the actual values in the cell(s) need to be moved. Then I need to revert to intercepting the events and move values between cells accordingly I guess – Caligola May 04 '21 at 14:25

0 Answers0