Description
I want to use a QTableView with a QAbstractTableModel/QSortFilterProxyModel to offer the possibility to select multiple table cells, make an edit and have this edit applied to the model representing all the selected table cells. In order to achieve this, the model's selectedIndexes is queried on the proxy model's dataChanged.
It seems that PySide2 has a bug because self.view.selectionModel().selectedIndexes() in the code below does not return the selected elements but an empty list when you edit the cells of the sorted QTableView column, resulting in not all selected cells updating as a result of an edit.
See here: https://imgur.com/a/KcHFU
For the other columns which are not the sorted one, the multi-select followed by edit updates the model and view properly
class MyTableModel(QtCore.QAbstractTableModel): def __init__(self, table_data, parent=None): QtCore.QAbstractTableModel.__init__(self, parent) self.table_data = table_data def rowCount(self, parent): return len(self.table_data) def columnCount(self, parent): return len(self.table_data[0]) def flags(self, index): original_flags = super(MyTableModel, self).flags(index) return original_flags | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable def data(self, index, role=QtCore.Qt.DisplayRole): if role == QtCore.Qt.DisplayRole: row = index.row() column = index.column() item = index.internalPointer() if item is not None: print(item) value = self.table_data[row][column] return value def setData(self, index, value, role=QtCore.Qt.EditRole): if role == QtCore.Qt.EditRole: row = index.row() column = index.column() self.table_data[row][column] = value self.dataChanged.emit(index, index) return True return QtCore.QAbstractTableModel.setData(self, index, value, role) class Widget(QtWidgets.QWidget): def __init__(self, *args, **kwargs): QtWidgets.QWidget.__init__(self, *args, **kwargs) self.view = QtWidgets.QTableView() self.setLayout(QtWidgets.QVBoxLayout()) self.layout().addWidget(self.view) table_data = [['one', 'two', 'three'], ['four', 'five', 'six']] proxy_model = QtCore.QSortFilterProxyModel() model = MyTableModel(table_data=table_data) proxy_model.setSourceModel(model) proxy_model.setDynamicSortFilter(True) self.view.setModel(proxy_model) proxy_model.dataChanged.connect(self.on_data_changed) self.view.setSortingEnabled(True) # requires proxy model self.view.sortByColumn(0, QtCore.Qt.AscendingOrder) self.view.horizontalHeader().setStretchLastSection(True) self.view.horizontalHeader().setSectionsMovable(True) def on_data_changed(self, _from, _to): model = _from.model() # proxy model model.blockSignals(True) for index in self.view.selectionModel().selectedIndexes(): model.setData(index, _from.data()) model.blockSignals(False) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) w = Widget() w.show() sys.exit(app.exec_())
Workaround:
class Widget(QtWidgets.QWidget): def __init__(self, *args, **kwargs): [...] self.view.horizontalHeader().setSectionsMovable(True) self.view.selectionModel().selectionChanged.connect(self.on_selectionChanged) self.currentSelected = [] def on_selectionChanged(self, selected, deselected): for ix in selected.indexes(): if ix not in self.currentSelected: self.currentSelected.append(ix) for ix in deselected.indexes(): if ix in self.currentSelected: self.currentSelected.remove(ix) def on_data_changed(self, _from, _to): model = _from.model() # proxy model model.blockSignals(True) pindexes = [QtCore.QPersistentModelIndex(ix) for ix in self.currentSelected] for index in pindexes: model.setData(index, _from.data()) model.blockSignals(False)