Search code examples
pythonuser-interfacepyside2qtableviewqabstracttablemodel

removing multiple rows in RemoveRow in QAbstractTableModel and refreshing - Pyside2


I have a QTableView using a QAbstractTableModel and I am trying create some remove row functionality. I have written in the removeRow code like so -

def removeRows(self, row, count, parent=QtCore.QModelIndex()):
    self.beginRemoveRows(parent, row, row + count - 1)
    for i in range(count):
        del self._data[row]
    self.endRemoveRows()
    return True

When calling a removeRow however it doesn't immediately refresh the table, it will only refresh after I click somewhere on the table. Do I need to call a layoutChanged.emit() after the removal is ran? and would I need to call a layoutAboutToBeChanged if I am calling a layoutChanged?

Also I wanted to be able to remove rows that are not in span. Like if the user cntrl selected random rows (like maybe row 1 and row 7 for example). Clearly this was designed to be setup to only delete rows in a span so not sure if its possible to delete them in random locations.

Edit: So i think I was able to set it up to remove random rows when give an list of rows, it seems to be working.

def removeRows(self, row_indexes, parent=QtCore.QModelIndex()):
    self.layoutAboutToBeChanged.emit()
    row_indexes.sort(reverse=True)
    for row in row_indexes:
        self.beginRemoveRows(parent, row, row)
        del self._data[row]
        self.endRemoveRows()
    self.layoutChanged.emit()
    return True

And to call it I do this

row_index = set()
selected_rows = self.output.selectedIndexes()
for row in selected_rows:
    row_index.add(row.row())
self.model.removeRows(list(row_index), self.output_list.currentIndex())

I added the layoutToBeChanged.emit() and layoutchanged.emit() before and after doing the removal, and it is now refreshing the table. Not sure if that is the right way to do it, without the layoutchange it doesn't refresh it automatically.

This is my entire QabstractTableModel class

class FileTableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
    super(FileTableModel, self).__init__()
    self._data = data
    self.headers = ['File Name', 'Path', 'File Size', 'Modified Date', 'Creation Date']

def data(self, index, role):
    if role == QtCore.Qt.DisplayRole:
        value = self._data[index.row()][index.column()]

        if isinstance(value, datetime):
            return datetime.strftime(value, "%m/%d/%Y %X %p")
        elif isinstance(value, int):
            return size_conversion(value)
        else:
            return value

def rowCount(self, index):
    return len(self._data)

def columnCount(self, index):
    return len(self._data[0])

def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
    if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
        return self.headers[section]

def sort(self, column, order=QtCore.Qt.AscendingOrder):
    self.layoutAboutToBeChanged.emit()
    self._data.sort(key=lambda x: x[column], reverse=order == QtCore.Qt.DescendingOrder)
    self.layoutChanged.emit()

def removeRows(self, row_indexes, parent=QtCore.QModelIndex()):
    self.layoutAboutToBeChanged.emit()
    row_indexes.sort(reverse=True)
    for row in row_indexes:
        self.beginRemoveRows(parent, row, row)
        del self._data[row]
        self.endRemoveRows()
    self.layoutChanged.emit()
    return True

Solution

  • Here's my own implementation for removing multiple rows. The list of rows is chopped up into continuous slices so the removal can be done as efficiently as possible.

    @staticmethod
    def continuous_slices(numbers):
        if not numbers:
            return
        numbers.sort(reverse=True)
        start_idx = 0
        for idx in range(1, len(numbers)):
            if numbers[idx - 1] > (numbers[idx] + 1):
                yield numbers[idx - 1], numbers[start_idx]
                start_idx = idx
        yield numbers[-1], numbers[start_idx]
    
    def removeRows(self, row_indices, parent=QtCore.QModelIndex()):
        for first, last in self.continuous_slices(row_indices):
            self.beginRemoveRows(parent, first, last)
            del self._data[first: last + 1]
            self.endRemoveRows()
        return True