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
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