Setup description
- Table in PySide created with
QMainWindow - > QWidget -> QTableView -> TableModel (QAbstractTableModel) -> array[object] - first column has QPushButton delegate ResetDelegate
- second column has QLineEdit delegate EditDelegate
Detailed description of functionality
- data structure keeps original value of row text
- column "changed" show "no" when text is not changed (compared to original text)
- when text is changed, first column displays button which resets the text to original
Problem description
- functionality as described above works only for last row
- when editing other then last row, debug print in EditDelegate->setModelData shows, that index of editor is still the index of last row, not the row edited
- this is so far as I got with debugging, but I'm not able to move further from there
Minimal functioning example:
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QLineEdit
from PySide6.QtWidgets import QTableView, QWidget, QStyledItemDelegate, QPushButton
from PySide6.QtCore import Qt, QAbstractTableModel, QItemSelectionModel, QEvent
class DataClass():
def __init__(self, text = ''):
self.originalText = text
self.text = text
class ResetDelegate(QStyledItemDelegate):
isClicked = False
def __init__(self, parent):
super(ResetDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
editor = QPushButton('Reset' , parent)
editor.clicked.connect(self.buttonClicked)
return editor
def setEditorData(self, editor, index):
if index.data():
editor.setAttribute(Qt.WA_TransparentForMouseEvents, False)
editor.setStyleSheet('')
editor.setFocusPolicy(Qt.StrongFocus)
if self.isClicked:
editor.setFocus()
self.parent().setCurrentIndex(index)
else:
editor.setAttribute(Qt.WA_TransparentForMouseEvents, True)
editor.setStyleSheet(
'color:transparent; background: transparent; border: none;')
editor.setFocusPolicy(Qt.NoFocus)
def setModelData(self, editor, model, index):
sender = self.sender()
if sender:
model.setData(index, None, role=Qt.EditRole)
def displayText(self, *args):
return 'no'
def buttonClicked(self):
self.isClicked = True
self.commitData.emit(self.sender())
self.isClicked = False
def eventFilter(self, editor, event):
if event.type() == event.Type.Wheel:
event.setAccepted(False)
return True
if event.type() == event.MouseMove:
editor.mouseMoveEvent(event)
event.setAccepted(True)
return True
return super().eventFilter(editor, event)
class UnselectedLineEdit(QLineEdit):
def __init__(self, parent):
super(UnselectedLineEdit, self).__init__(parent)
def event(self, event):
if event.type() == QEvent.Show:
self.deselect()
return super(UnselectedLineEdit, self).event(event)
class EditDelegate(QStyledItemDelegate):
def __init__(self, parent):
super(EditDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
editor = UnselectedLineEdit(parent)
editor.setClearButtonEnabled(True)
editor.textChanged.connect(self.text)
self.editor = editor
return editor
def text(self, string: str):
self.commitData.emit(self.editor)
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
print(f"Edit Delegate setModelData [{index.row()}, {index.column()}]")
model.setData(index, editor.text(), role=Qt.EditRole)
class TableModel(QAbstractTableModel):
tabPressed = False
def __init__(self, localData=[[]], parent=None):
super().__init__(parent)
self.modelData = localData
def headerData(self, section: int, orientation: Qt.Orientation, role: int):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section == 0:
return 'Changed'
if section == 1:
return 'Value'
if orientation == Qt.Vertical:
return str(section)
def columnCount(self, parent=None):
return 2
def rowCount(self, parent=None):
return len(self.modelData)
def data(self, index, role=Qt.DisplayRole):
column = index.column()
row = index.row()
# Reset
if column == 0 and role == Qt.DisplayRole:
return self.modelData[row].text != self.modelData[row].originalText
# Edit
if column == 1 and (role == Qt.DisplayRole or role == Qt.EditRole):
return self.modelData[row].text
def setData(self, index, value = None, role=Qt.DisplayRole):
# Reset
if index.column() == 0 and role == Qt.EditRole:
self.modelData[row].text = self.modelData[row].originalText
# refreshing both Reset and Edit
indexReset = self.index(index.row(), 0)
indexEdit = self.index(index.row(), 1)
self.dataChanged.emit(indexReset, indexEdit)
return True
# Edit
if index.column() == 1 and (role == Qt.EditRole or role == Qt.DisplayRole) :
print(value)
self.modelData[row].text = value
print(self.modelData[row].originalText)
print(self.modelData[row].text)
# refreshing only Reset
indexReset = self.index(index.row(), 0)
self.dataChanged.emit(indexReset, indexReset)
return True
return False
def flags(self, index):
flags = super().flags(index)
if index.column() == 0 and self.tabPressed and not self.data(index, Qt.DisplayRole):
flags &= ~(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
return flags
app = QApplication()
data = []
for i in range(0, 4):
data.append(DataClass('test'))
model = TableModel(data)
tableView = QTableView()
tableView.setModel(model)
selectionModel = QItemSelectionModel(model)
tableView.setSelectionModel(selectionModel)
tableView.setItemDelegateForColumn(0, ResetDelegate(tableView))
tableView.setItemDelegateForColumn(1, EditDelegate(tableView))
for row in range(0, tableView.model().rowCount()):
tableView.openPersistentEditor(tableView.model().index(row, 0))
tableView.openPersistentEditor(tableView.model().index(row, 1))
widget = QWidget()
widget.horizontalHeader = tableView.horizontalHeader()
widget.horizontalHeader.setStretchLastSection(True)
widget.mainLayout = QVBoxLayout()
widget.mainLayout.setContentsMargins(1,1,1,1)
widget.mainLayout.addWidget(tableView)
widget.setLayout(widget.mainLayout)
mainWindow = QMainWindow()
mainWindow.setCentralWidget(widget)
mainWindow.setGeometry(0, 0, 300, 300)
mainWindow.show()
exit(app.exec())