Search code examples
pythonpysidemayapyside2

QAbstratctTableModel - removeRows


Code is almost complete. Here's the deal:

It is python and PySide. I have a QAbstractTableModel and a QTableView.

I cant get deleting rows correctly. I think the problem is somewhere in the indexes of the rows onde I delete one of them...

here is the button delegate I use:

class ButtonDelegate(QItemDelegate):

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

    def paint(self, painter, option, index):

        widget = QWidget()
        layout = QHBoxLayout()
        widget.setLayout(layout)
        btn = QPushButton("X")
        btn.clicked.connect(partial(self.parent().cellButtonClicked, index))
        layout.addWidget(btn)
        layout.setContentsMargins(2,2,2,2)

        if not self.parent().indexWidget(index):
            self.parent().setIndexWidget(index, widget)

here's the cellButtonClicked method, it is under the table view:

class Table(QTableView):

def __init__(self, *args, **kwargs):
    QTableView.__init__(self, *args, **kwargs)

    self.setItemDelegateForColumn(6, ButtonDelegate(self))
    self.setItemDelegateForColumn(0, EmptyDelegate(self))

    self.setSortingEnabled(True)

def cellButtonClicked(self, index,  *args):

    model = self.model()
    model.removeRow(index.row())

and here is the model removeRow Method:

def removeRow(self, row, parent = QtCore.QModelIndex()):

    self.beginRemoveRows(parent, row, row)

    array = []
    for i in range(7):
        if i == 0:
            array.append(self.index(row, i).data())
        else:
            array.append(str(self.index(row, i).data()))

    self.cycles.remove(array)

    self.endRemoveRows()

    # update custom node in maya. 
    self.getData()

I think that, mainly, the problem is that when I delete a row it does not update the indexes of the model. So when I click again in any delete button it starts de removeRow() with an index the does no match the rowCount of the model anymore, therefore I can't build the array to be removed from the model data.

Did it make sense? if you need more code, tell me what you need.


Solution

  • The problem is caused because you have set the value of the row when you have created each delegate, so its value is not updated.

    A possible solution is to use a lambda function to pass a QPersistenModelIndex associated with the temporary QModelIndex, but I have seen that there is an unexpected behavior that is creating a selection, so I called clearSelection().

    It is not necessary to connect to the cellButtonClicked slot since you can directly access the model using QModelIndex or QPersistenModelIndex.

    class ButtonDelegate(QItemDelegate):
        def __init__(self, parent):
            QItemDelegate.__init__(self, parent)
    
        def paint(self, painter, option, index):
            widget = QWidget()
            layout = QHBoxLayout()
            widget.setLayout(layout)
            btn = QPushButton("X")
            ix = QPersistentModelIndex(index)
            btn.clicked.connect(lambda ix = ix : self.onClicked(ix))
            layout.addWidget(btn)
            layout.setContentsMargins(2,2,2,2)
            if not self.parent().indexWidget(index):
                self.parent().setIndexWidget(index, widget)
    
        def onClicked(self, ix):
            model = ix.model()
            model.removeRow(ix.row())
            self.parent().clearSelection()
    

    Another option is to handle the clicked events through editorEvent since the provided QModelIndex has updated values as shown below:

    class ButtonDelegate(QStyledItemDelegate):
        def __init__(self, parent):
            QStyledItemDelegate.__init__(self, parent)
            self.state = QStyle.State_Enabled
    
        def paint(self, painter, option, index):
            button = QStyleOptionButton()
            button.rect = self.adjustRect(option.rect)
            button.text = "X"
            button.state = self.state
            QApplication.style().drawControl(QStyle.CE_PushButton, button, painter)
    
        def editorEvent(self, event, model, option, index):
            if event.type() == QEvent.Type.MouseButtonPress:
                self.state = QStyle.State_On
                return True
            elif event.type() == QEvent.Type.MouseButtonRelease:
                r = self.adjustRect(option.rect)
                if r.contains(event.pos()):
                    model.removeRow(index.row())
                self.state = QStyle.State_Enabled
            return True
    
        @staticmethod
        def adjustRect(rect):
            r = QRect(rect)
            margin = QPoint(2, 2)
            r.translate(margin)
            r.setSize(r.size()-2*QSize(margin.x(), margin.y()))
            return r
    

    In addition to this it is not necessary to iterate through data(), we can delete the row directly:

    def removeRow(self, row, parent=QModelIndex()):
        self.beginRemoveRows(parent, row, row)
        self.cycles.remove(self.cycles[row])
        self.endRemoveRows()
        self.getData()
    

    In the following link both options are implemented.