Reputation: 10281
Using QTableView
and QAbstractTableModel
, I wish to be able to select multiple cells in a table and make an edit affect all those selected cells. This is how I currently are doing it, which involves passing view
(QTableView
) and proxy_model
(QSortFilterProxyModel
) into the class, so that I can access them (in order to acquire the appropriate rows and columns):
import sys
from pprint import pprint
try:
from PySide2 import QtWidgets, QtCore
except ImportError:
from PyQt5 import QtWidgets, QtCore
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self,
view,
proxy_model,
table_data,
parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.table_data = table_data
self.view = view
self.proxy_model = proxy_model
def rowCount(self, parent):
return len(self.table_data)
def columnCount(self, parent):
return len(self.table_data[0])
def flags(self, index):
# Original, inherited flags:
original_flags = super(MyTableModel, self).flags(index)
return original_flags | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role):
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
return None
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
selection_model = self.view.selectionModel()
selected_indexes = selection_model.selectedIndexes()
for selected_index in selected_indexes:
mapped_index = self.proxy_model.mapToSource(selected_index)
selected_row = mapped_index.row()
selected_column = mapped_index.column()
self.table_data[selected_row][selected_column] = value
pprint(self.table_data)
self.dataChanged.emit(index, selected_index) # from, to
return True
return False
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
table_data = [['one', 'two', 'three'], ['four', 'five', 'six']]
view = QtWidgets.QTableView()
proxy_model = QtCore.QSortFilterProxyModel()
model = MyTableModel(view=view, proxy_model=proxy_model, table_data=table_data)
proxy_model.setSourceModel(model)
proxy_model.setDynamicSortFilter(True)
view.setModel(proxy_model)
view.setSortingEnabled(True) # requires proxy model
view.sortByColumn(0, QtCore.Qt.AscendingOrder)
view.horizontalHeader().setStretchLastSection(True)
view.horizontalHeader().setSectionsMovable(True)
view.show()
app.exec_()
I suspect that I don't have to pass view
and proxy_model
into the class, and that I can access these objects in some other way. Is this possible, and if so - how?
I know my example is Python-specific, but my question is really a binding-agnostic question and so I'm also tagging my question with qt
.
Upvotes: 2
Views: 1093
Reputation: 243973
The base model should not know the view or the proxy, so you should have something similar to the following:
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)
The proxy has methods to pass the changes to the original model so in general it is not necessary to access the base model to make the changes but the same proxy, to have an order I implemented a widget and I used the dataChanged()
method, this could cause an infinite loop so we must block other dataChanged()
signals for it we use blockSignals()
.
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_())
Update:
It seems that PySide2
has a bug because it does not return the selected elements but an empty list so it does not enter the for loop, before we create that list for it we use the selectionChanged
signal of the selectionModel()
, this does not happen in PyQt5.:
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)
Upvotes: 1