0

Here's my setup:

enter image description here

I have a eventFilter on my QTableWidget to handle both mousePress and mouseRelease events.

I also have a custom class for the QPushButton to handle mousePress and mouseRelease events.

However when I trigger the mousePress event by clicking and holding on the button, the QTableWidget doesn't see the event.

Just in case here's the code for both the custom QPushButton class and QTableWidget eventFilter.

class Button(QPushButton):
    key = None

    def __init__(self, title, parent):
        super().__init__(title, parent)

    def mousePressEvent(self, e):

        super().mousePressEvent(e)

        if e.button() == Qt.LeftButton:
            print('press')


class TableWidget(QTableWidget):

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

    def eventFilter(self, source, event):
        if event.type() == QEvent.MouseButtonRelease:
            print("mouse release")
        return QTableWidget.eventFilter(self, source, event)

What I'm hoping to see is when I press the custom button and then release mouse, that it will print "mouse release" in the console.

This doesn't happen.

But it does print it successfully when I click anywhere in the table except the button.

Let me know in the comments if you want me to add any extra information.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
John Smith
  • 851
  • 2
  • 10
  • 22

1 Answers1

1

Explanation:

As I already pointed out in this post: The handling of mouse events between the widgets goes from children to parents, that is, if the child does not accept the event (it does not use it) then it will pass the event to the parent.. That can be verified using the following example:

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QPushButton,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QWidget,
)


class TableWidget(QTableWidget):
    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        print("released")


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.tablewidget = TableWidget(4, 4)

        container = QWidget()
        lay = QVBoxLayout(container)
        lay.addWidget(QLabel("QLabel", alignment=Qt.AlignCenter))
        lay.addWidget(QPushButton("QPushButton"))
        container.setFixedSize(container.sizeHint())

        item = QTableWidgetItem()
        self.tablewidget.setItem(0, 0, item)
        item.setSizeHint(container.sizeHint())
        self.tablewidget.setCellWidget(0, 0, container)
        self.tablewidget.resizeRowsToContents()
        self.tablewidget.resizeColumnsToContents()

        lay = QVBoxLayout(self)
        lay.addWidget(self.tablewidget)


def main():
    import sys

    app = QApplication(sys.argv)

    widget = Widget()
    widget.resize(640, 480)
    widget.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

If the QPushButton is pressed then the mouseReleaseEvent method of the QTableWidget will not be invoked because the QPushButton consumes it, unlike when the QLabel is pressed since it does not consume it and the QTableWidget does.

Solution:

As I pointed out in the other post, a possible solution is to use an eventfilter to the QWindow and to be able to filter by verifying that the click is given on a cell.

from PyQt5.QtCore import (
    pyqtSignal,
    QEvent,
    QObject,
    QPoint,
    Qt,
)
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QPushButton,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QWidget,
)


class MouseObserver(QObject):
    pressed = pyqtSignal(QPoint, QPoint)
    released = pyqtSignal(QPoint, QPoint)
    moved = pyqtSignal(QPoint, QPoint)

    def __init__(self, window):
        super().__init__(window)
        self._window = window

        self.window.installEventFilter(self)

    @property
    def window(self):
        return self._window

    def eventFilter(self, obj, event):
        if self.window is obj:
            if event.type() == QEvent.MouseButtonPress:
                self.pressed.emit(event.pos(), event.globalPos())
            elif event.type() == QEvent.MouseMove:
                self.moved.emit(event.pos(), event.globalPos())
            elif event.type() == QEvent.MouseButtonRelease:
                self.released.emit(event.pos(), event.globalPos())
        return super().eventFilter(obj, event)


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.tablewidget = QTableWidget(4, 4)

        container = QWidget()
        lay = QVBoxLayout(container)
        lay.addWidget(QLabel("QLabel", alignment=Qt.AlignCenter))
        lay.addWidget(QPushButton("QPushButton"))
        container.setFixedSize(container.sizeHint())

        item = QTableWidgetItem()
        self.tablewidget.setItem(0, 0, item)
        item.setSizeHint(container.sizeHint())
        self.tablewidget.setCellWidget(0, 0, container)
        self.tablewidget.resizeRowsToContents()
        self.tablewidget.resizeColumnsToContents()

        lay = QVBoxLayout(self)
        lay.addWidget(self.tablewidget)

    def handle_window_released(self, window_pos, global_pos):
        lp = self.tablewidget.viewport().mapFromGlobal(global_pos)
        ix = self.tablewidget.indexAt(lp)
        if ix.isValid():
            print(ix.row(), ix.column())


def main():
    import sys

    app = QApplication(sys.argv)

    widget = Widget()
    widget.resize(640, 480)
    widget.show()

    mouse_observer = MouseObserver(widget.windowHandle())
    mouse_observer.released.connect(widget.handle_window_released)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thing is there are actually 2 buttons in a single cell, and I'll need to be able to tell which of the two was pressed. Is there a work around to detect which button of the two in a single cell was pressed? and be able to record press, and release mouse not just from the button (because I'm planning to drag the button eventually with custom behaviour) Perhaps there's a way to reject the event / not consume it? – John Smith Jul 30 '21 at 18:54
  • @VadimTatarnikov As I already pointed out in a previous comment: If you want help, provide a [mre]. It seems that you have a special requirement so you need to explain in more detail so I invite you to create a new post. Please read [ask] – eyllanesc Jul 30 '21 at 19:12
  • Yeah I was planning to add more details, it's just fairly complex so I need to find time to break it down to a more simple form. – John Smith Jul 30 '21 at 19:27
  • 1
    @VadimTatarnikov I recommend you not edit your original post but create a new post since you point it out to me goes beyond what your current post describes. Please read the links – eyllanesc Jul 30 '21 at 19:30