0

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

musicamante
  • 41,230
  • 6
  • 33
  • 58
martin_h
  • 29
  • 5
  • 1
    Creating a reference for `self.editor` is wrong, as it will always be overwritten when a new editor is created (so, you always get the *last*). Use `self.sender()` in `text()` or `editor.textChanged.connect(lambda: self.commitData.emit(editor)`. – musicamante Feb 07 '22 at 18:04
  • @musicamante Great! It works! Thank you very much! – martin_h Feb 08 '22 at 06:33

0 Answers0