qtscrollpysideqtableviewqstyleditemdelegate

QTableView not scrolling when mouse is at disabled delegate button


Setup description

Problem description
If button in delegate is disabled [setEnabled(False)], the scrolling of table is not working when mouse is above the disabled button.

Other

Tested with PySide6 (6.2.2.1), Python 3.9, Windows 10, Debian

Possibly the same problem (but no solution listed):
https://forum.qt.io/topic/123495/disabled-widget-hogs-scrollevents-how-to-disable

Minimal functioning example:

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

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

    def paint(self, painter, option, index):
        self.parent().openPersistentEditor(index)
        super(ButtonDelegate, self).paint(painter, option, index)

    def createEditor(self, parent, option, index):
        editor = QPushButton("Button", parent)
        editor.setEnabled(False)
        return editor

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 len(self.modelData[0])

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

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

app = QApplication()

data = [['1', '2'],['1', '2'],['1', '2'],['1', '2'],['1', '2'],['1', '2'],['1', '2'],
    ['1', '2'],['1', '2'],['1', '2'],['1', '2'],['1', '2'],['1', '2'],['1', '2'],
    ['1', '2'],['1', '2'],['1', '2'],['1', '2']]

model = TableModel(data)

tableView = QTableView()
tableView.setModel(model)
selectionModel = QItemSelectionModel(model)
tableView.setSelectionModel(selectionModel)
tableView.setItemDelegateForColumn(1, ButtonDelegate(tableView))


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

Solution

  • I can reproduce this with PyQt6 so I can confirm that it's related to Qt6

    The difference is that a Wheel event is always accepted on disabled buttons in Qt6 while it's ignored on Qt5, and it's related to the special behavior of QAbstractButton for input events. This has been actually considered a wrong behavior for Qt5, so it cannot be considered a bug (see QTBUG-79102 and QTBUG-67032).

    The solution is to set the set the event as not accepted in the event filter of the delegate, and return True to propagate the event to the parent:

    class ButtonDelegate(QStyledItemDelegate):
        def eventFilter(self, obj, event):
            if event.type() == event.Type.Wheel:
                event.setAccepted(False)
                return True
            return super().eventFilter(obj, event)
    

    Note that opening the editor in the paint function is not a good idea: while the function usually is "ignored" when the editor is already open, any operation that can cause repaint (and, potentially, geometry changes) should never happen inside painting functions.