How to catch mouse over event of QTableWidget item in pyqt?
Asked Answered
R

4

1

what I want to do is to change the color of a QTableWidget item, when I hover with the mouse over the item of my QTableWidget.

Rebak answered 19/11, 2013 at 6:47 Comment(0)
U
9

Firstly, the table widget needs to have mouse-tracking switched on to get the hover events.

Secondly, we need to find some signals that tell us when the mouse enters and leaves the table cells, so that the background colours can be changed at the right times.

The QTableWidget class has the cellEntered / itemEntered signals, but there is nothing for when the mouse leaves a cell. So, we will need to create some custom signals to do that.

The TableWidget class in the demo script below sets up the necessary cellExited / itemExited signals, and then shows how everything can be hooked up to change the item background when hovering with the mouse:

from PyQt4 import QtGui, QtCore

class TableWidget(QtGui.QTableWidget):
    cellExited = QtCore.pyqtSignal(int, int)
    itemExited = QtCore.pyqtSignal(QtGui.QTableWidgetItem)

    def __init__(self, rows, columns, parent=None):
        QtGui.QTableWidget.__init__(self, rows, columns, parent)
        self._last_index = QtCore.QPersistentModelIndex()
        self.viewport().installEventFilter(self)

    def eventFilter(self, widget, event):
        if widget is self.viewport():
            index = self._last_index
            if event.type() == QtCore.QEvent.MouseMove:
                index = self.indexAt(event.pos())
            elif event.type() == QtCore.QEvent.Leave:
                index = QtCore.QModelIndex()
            if index != self._last_index:
                row = self._last_index.row()
                column = self._last_index.column()
                item = self.item(row, column)
                if item is not None:
                    self.itemExited.emit(item)
                self.cellExited.emit(row, column)
                self._last_index = QtCore.QPersistentModelIndex(index)
        return QtGui.QTableWidget.eventFilter(self, widget, event)

class Window(QtGui.QWidget):
    def __init__(self, rows, columns):
        QtGui.QWidget.__init__(self)
        self.table = TableWidget(rows, columns, self)
        for column in range(columns):
            for row in range(rows):
                item = QtGui.QTableWidgetItem('Text%d' % row)
                self.table.setItem(row, column, item)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.table)
        self.table.setMouseTracking(True)
        self.table.itemEntered.connect(self.handleItemEntered)
        self.table.itemExited.connect(self.handleItemExited)

    def handleItemEntered(self, item):
        item.setBackground(QtGui.QColor('moccasin'))

    def handleItemExited(self, item):
        item.setBackground(QtGui.QTableWidgetItem().background())

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window(6, 3)
    window.setGeometry(500, 300, 350, 250)
    window.show()
    sys.exit(app.exec_())
Ukase answered 19/11, 2013 at 19:16 Comment(3)
Why do we NEED to create signals for dealing with the mouse leaving cells? What is wrong in the approach of using a variable to track the cell currently hovered?Minerva
@Vicent. I was aiming for a more general solution. I think the cellExited/itemExited signals are useful in their own right, and was slightly surprised that Qt doesn't provide them (but maybe I'm missing something). The problem with trying to track the current cell, is that there is no notification when leaving the viewport (because there is no new cell to enter). So when leaving the table's viewport, the last cell will stay highlighted (which may not be what is expected/wanted).Ukase
@Ukase I asked the same question but wrt QTreeView of QStandardItemModel: #28803263. My hunch is it's an easy modification of your code, but thought it better to make it a separate question.Fig
M
4

You can achieve your goal pretty easily using the proper signals as proved by the following simple code:

from PyQt4.QtGui import *
from PyQt4.QtCore import *

class TableViewer(QMainWindow):
    def __init__(self, parent=None):
        super(TableViewer, self).__init__(parent)
        self.table = QTableWidget(3, 3)
        for row in range (0,3):
            for column in range(0,3):
                item = QTableWidgetItem("This is cell {} {}".format(row+1, column+1))
                self.table.setItem(row, column, item)
        self.setCentralWidget(self.table)

        self.table.setMouseTracking(True)

        self.current_hover = [0, 0]
        self.table.cellEntered.connect(self.cellHover)

    def cellHover(self, row, column):
        item = self.table.item(row, column)
        old_item = self.table.item(self.current_hover[0], self.current_hover[1])
        if self.current_hover != [row,column]:
            old_item.setBackground(QBrush(QColor('white')))
            item.setBackground(QBrush(QColor('yellow')))
        self.current_hover = [row, column]


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    tv = TableViewer()
    tv.show()
    sys.exit(app.exec_())

You may be interested in other signals too, especially itemEntered. However, if you want total control over the editing and display of items then using delegates (via the QTableWidget.setItemDelegate method) is strongly recommended.

UPDATE:

sorry, I had forgotten the second part of the problem i.e. what happens when the mouse exits a cell. Even then the problem can be solved easily without using events. See the updated code, please.

Minerva answered 19/11, 2013 at 16:0 Comment(3)
+1 for fixing the second part, but it would be nicer if the background of the last cell was restored when leaving the table's viewport.Ukase
How to achieve the for QTableView ? QTableView doesn't have setMouseTracking method.Midden
@san setMouseTracking is a method of the QWidget class. QTableView inherits QWidget so you can use the method with your QTableView widgets too.Minerva
C
1

There are no events based on QTableWidgetItem, but you can do this:

  • reimplement the mouseMoveEvent() of QTableWidget, you can get the mouse position;
  • use itemAt() method to get the item under your mouse cursor;
  • customize your item;

This may simalute what you want.

Converted answered 19/11, 2013 at 9:44 Comment(2)
I want the picture in the cell I have using QImage to scale up to some extent and when I take mouse out it should restore back to size set.Midden
@san as only one item can be under the cursor, so you can keep the item as a private member current_hovered_item to indicate the current hovered item, and in the beginning of the mouseMoveEvent, check if the current hovered item is the item stored in current_hovered_item, if not, you may want to scale the image back with the old item.Converted
A
1

I know this is old but wanted to update a couple of parts to it as I came across this page looking for a similar solution. This has a couple of parts to it, one is similar to the above but avoids the NoneType error if the cell is empty. Additionally, it will change the color of the highlighted cell, but also update a tooltip for the cell to display the contents of the cell in a tooltip. Nice if you have cells with runoffs 123...

Sure it could be cleaned up a bit, but works for PyQt5. Cheers!

def cellHover(self, row, column):
    item = self.My_Table1.item(row, column)
    old_item = self.My_Table1.item(self.current_hover[0], self.current_hover[1])
    if item is not None:
        if self.current_hover != [row,column]:
            text = item.text()
            if text is not None:
                self.My_Table1.setToolTip(text)
                item.setBackground(QBrush(QColor('#bbd9f7')))
                old_item.setBackground(QBrush(QColor('white')))
            self.current_hover = [row, column]
Antiparallel answered 12/5, 2019 at 22:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.