'QStyledItemDelegate disabling button in last column moves selection to next row

Setup description

  • Table in PySide created with
    QMainWindow - > QWidget -> QTableView -> TableModel (QAbstractTableModel) -> array[]
  • For second and third column is created ButtonDelegate(QStyledItemDelegate)
  • buttons toggle value in the first column - but butons must be separate for specific reason in my application (toggling with one button is not a solution)
  • button with value of the first column is "hidden"
  • only whole single row is selected (important in my application where I'm separately showing detailed data on the selected row)

Detailed description of functionality

  • Buttons in my application don't necesserily toggle value. Easiest to explaining fonctionality of my application is something like configuration list.
  • Initially in the list are generic items which can be selected and the two buttons are "+" (add/select) and "-" (remove/deselect).
  • Some items can be added only once, in that case the buttons are really only toggling the item selection. If not selected only the button "+" is show and if selected only button "-" is shown.
  • Some items can be added multiple times. In that case initially the item is unseleted. Presing "+" selects the item, shows "-" buton, but button "+" is still shown, as the item can be added multiple times. When pressed "+" once again, the next row with the same item is added, again with both "+" and "-" shown. Then pressing "-" works in reverse way, removing row where "-" is pressed until last item of the same type, where "-" results in unselected item. Therefore function of +/- is content dependent.
  • There few reasons I decided to have buttons in separate columns - keep possibility to sort based on selection state, header to show "Add" for "+" and "Remove" for "-"

Problem description

  • when button in last column is disabled (pushing False button and then True button), the selection moves to next row - should remain in the same
  • also, probably the showing and hiding of active button should be done in paint (instead of the openPersistentEditor). I was looking in the documentation and examples from google to find way how to, but still I haven't figured it out. If you could show me how, I would appreciate it. Also if you have link to some good tutorial on this topic (paint) I would be glad, because still I'm not getting how to use it.

Minimal functioning example:

from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QAbstractItemView
from PySide6.QtWidgets import  QTableView, QWidget, QStyledItemDelegate, QPushButton
from PySide6.QtCore import Qt, QModelIndex, QAbstractTableModel, QItemSelectionModel

class TrueButtonDelegate(QStyledItemDelegate):
    
    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)    

    def paint(self, painter, option, index):
        self.parent().openPersistentEditor(index) # this should be somewhere else, not in paint

    def createEditor(self, parent, option, index):
        editor = QPushButton('True', parent)
        editor.setEnabled(False)
        editor.clicked.connect(self.buttonClicked)
        return editor
    
    def setEditorData(self, editor, index):
        if not index.data():
            editor.setText('True')
            editor.setEnabled(True)
            editor.setFlat(False)
            
        else:
            editor.setText('')
            editor.setEnabled(False)
            editor.setFlat(True)
    
    def setModelData(self, editor, model, index):
        model.setData(index, True, role=Qt.EditRole)
        
    def buttonClicked(self):
        self.commitData.emit(self.sender())    
    
    
    def eventFilter(self, obj, event):
        if event.type() == event.Type.Wheel:
            event.setAccepted(False)
            return True
        return super().eventFilter(obj, event)

class FalseButtonDelegate(QStyledItemDelegate):
    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)    

    def paint(self, painter, option, index):
        self.parent().openPersistentEditor(index) # this should be somewhere else, not in paint

    def createEditor(self, parent, option, index):
        editor = QPushButton('False', parent)
        editor.setEnabled(True)
        editor.clicked.connect(self.buttonClicked)
        return editor
    
    def setEditorData(self, editor, index):
        if index.data():
            editor.setText('False')
            editor.setEnabled(True)
            editor.setFlat(False)
            
        else:
            editor.setText('')
            editor.setEnabled(False)
            editor.setFlat(True)
    
    def setModelData(self, editor, model, index):
        model.setData(index, False, role=Qt.EditRole)
        
    def buttonClicked(self):
        self.commitData.emit(self.sender())    
    
    
    def eventFilter(self, obj, event):
        if event.type() == event.Type.Wheel:
            event.setAccepted(False)
            return True
        return super().eventFilter(obj, event)

class TableModel(QAbstractTableModel):
    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.Vertical:
                return "Row " + str(section)

    def columnCount(self, parent=None):
        return 3

    def rowCount(self, parent=None):
        return len(self.modelData)

    def data(self, index: QModelIndex, role: int):
        if role == Qt.DisplayRole:
            row = index.row()
            return self.modelData[row]
    
    def setData(self, index, value = None, role=Qt.DisplayRole):
        row = index.row()
        self.modelData[row] = value

        index = self.index(row, 0)
        self.dataChanged.emit(index, index) 
        index = self.index(row, 1)
        self.dataChanged.emit(index, index) 
        index = self.index(row, 2)
        self.dataChanged.emit(index, index) 

        return True

app = QApplication()

data = [True, True, True, True, True, True, True, True, True, True, True, True, True, True]

model = TableModel(data)

tableView = QTableView()
tableView.setModel(model)
selectionModel = QItemSelectionModel(model)
tableView.setSelectionModel(selectionModel)
tableView.setItemDelegateForColumn(1, FalseButtonDelegate(tableView))
tableView.setItemDelegateForColumn(2, TrueButtonDelegate(tableView))
tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
tableView.setSelectionMode(QAbstractItemView.SingleSelection)


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, 380, 300)
mainWindow.show()


exit(app.exec())


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source