Search code examples
python-3.xqtpyqtpyqt5pyside2

how to implement hover effect on a QTableView row



import sys
from PySide2.QtGui import QColor, QBrush
from PySide2.QtWidgets import QStyledItemDelegate, QApplication, QTableView, QAbstractItemView, QStyle
from PySide2.QtCore import Qt, QModelIndex, QAbstractTableModel, QEvent



class TBV(QTableView):
    def __init__(self) -> None:
        super().__init__()
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setMouseTracking(True)


class DEG(QStyledItemDelegate):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.currentHoveredRow = -1

    def paint(self, painter, option, index: QModelIndex) -> None:
        if option.state & QStyle.State_MouseOver:
            if self.currentHoveredRow == index.row():
                option.backgroundBrush = QBrush(QColor(Qt.red))
                # option.palette.setBrush(option.palette.Base, QBrush(QColor(Qt.red)))

        return super().paint(painter, option, index)
    
    def editorEvent(self, event, model, option, index: QModelIndex) -> bool:
        if event.type() == QEvent.MouseMove:
            if index.row() != self.currentHoveredRow:
                self.currentHoveredRow = index.row()
                self.parent().viewport().update()

        return super().editorEvent(event, model, option, index)



class MDL(QAbstractTableModel):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self._tabledata = [['mike', 12], ['kk', 13], ['jane', 14]]

    def rowCount(self, parent: QModelIndex = ...) -> int:
        return len(self._tabledata)

    def columnCount(self, parent: QModelIndex = ...) -> int:
        return len(self._tabledata[0] if self._tabledata else [])

    def data(self, index: QModelIndex, role: int = ...):
        if not index.isValid():
            return None
        if 0 == self.rowCount():
            return None
        if index.row() >= self.rowCount():
            return None
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return self._tabledata[index.row()][index.column()]

        else:
            return None

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = TBV()
    demo.setItemDelegate(DEG(demo))
    model = MDL(demo)
    demo.setModel(model)
    demo.show()
    sys.exit(app.exec_())

as the title described, I need to set a hover effect on the hovered row. I guess such visual effect cannot be set only be QSS. therefore, how to do it in the python code.

And the code I provide above, the editorEvent method will receive MouseMove Event and get the hovered row index, later the update method will trigger the paint method, however,

option.backgroundBrush = QBrush(QColor(Qt.red))
# option.palette.setBrush(option.palette.Base, QBrush(QColor(Qt.red)))

these code will not change the background color of the hovered row. I don't know how.


Solution

  • Setting the backgroundBrush of the option is ineffective, because paint() will create a copy of the option and initialize it with initStyleOption().

    What you can do is to override initStyleOption() instead and then set the background in case:

    • the state is State_MouseOver (as you already did);
    • any other "sibling" in the same row possibly contains the mouse cursor;
    class DEG(QStyledItemDelegate):
        def initStyleOption(self, option, index):
            super().initStyleOption(option, index)
    
            model = index.model()
            col = index.column()
            view = self.parent()
    
            if option.state & QStyle.State_MouseOver:
                option.backgroundBrush = QBrush(Qt.red)
            else:
                pos = view.viewport().mapFromGlobal(QCursor.pos())
                for c in range(model.columnCount()):
                    if c != col:
                        r = view.visualRect(index.siblingAtColumn(c))
                        if r.adjusted(0, 0, 1, 1).contains(pos):
                            option.backgroundBrush = QBrush(Qt.red)
                            break
    

    Note that it should not be necessary to override editorEvent(), but, even if it was, you should call the update() in any case, even if the movement does not affect a cell previously hovered.

    In any case, you cannot use editorEvent() alone for updating, because it does not track leave events, meaning that rows will not be updated if the mouse leaves moves to a blank area of the view.