处理父小部件和子小部件上的鼠标事件(按钮不消耗事件?)

Posted

技术标签:

【中文标题】处理父小部件和子小部件上的鼠标事件(按钮不消耗事件?)【英文标题】:Handle mouse events on both parent and child widgets (button don't consume event?) 【发布时间】:2021-10-05 10:22:19 【问题描述】:

这是我的设置:

我的QTableWidget 上有一个eventFilter 来处理mousePressmouseRelease 事件。

我还有一个自定义类供 QPushButton 处理 mousePressmouseRelease 事件。

但是,当我通过单击并按住按钮触发 mousePress 事件时,QTableWidget 看不到该事件。

以防万一这是自定义 QPushButton 类和 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)

我希望看到的是,当我按下自定义按钮然后释放鼠标时,它会在控制台中打印“鼠标释放”。

这不会发生。

但是当我单击表格中除按钮之外的任何位置时,它确实会成功打印。

如果您希望我添加任何额外信息,请在 cmets 中告诉我。

【问题讨论】:

【参考方案1】:

解释:

正如我在this post: 中已经指出的那样小部件之间鼠标事件的处理是从子级到父级的,也就是说,如果子级不接受该事件(它不使用它),那么它将将事件传递给父级。。可以使用以下示例进行验证:

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

如果 QPushButton 被按下,则不会调用 QTableWidget 的 mouseReleaseEvent 方法,因为 QPushButton 会消耗它,这与按下 QLabel 时不同,因为它不消耗它而 QTableWidget 消耗它。

解决方案:

正如我在另一篇文章中指出的那样,一种可能的解决方案是对 QWindow 使用事件过滤器,并能够通过验证是否在单元格上单击来进行过滤。

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

【讨论】:

问题是在一个单元格中实际上有 2 个按钮,我需要能够分辨出这两个按钮中的哪一个被按下。是否有解决方法来检测单个单元格中的两个按钮被按下?并且能够记录新闻,并且不仅可以从按钮上释放鼠标(因为我打算最终使用自定义行为拖动按钮)也许有一种方法可以拒绝事件/不使用它? @VadimTatarnikov 正如我在之前的评论中已经指出的那样:如果您需要帮助,请提供minimal reproducible example。看来你有特殊要求,所以你需要更详细地解释,所以我邀请你创建一个新的帖子。请阅读How to Ask 是的,我正计划添加更多细节,它只是相当复杂,所以我需要找时间将其分解为更简单的形式。 @VadimTatarnikov 我建议您不要编辑原始帖子,而是创建一个新帖子,因为您向我指出的内容超出了您当前帖子的描述。请阅读链接

以上是关于处理父小部件和子小部件上的鼠标事件(按钮不消耗事件?)的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:在 Navigation.pop 上刷新父小部件

在子小部件中,如何在 kivy 中获取父小部件的实例

如何从 Flutter 中的子小部件调用父小部件功能

Flutter - 无法从父小部件更改子状态

如何在颤动中从父小部件调用子小部件的initState()方法

如何将父小部件焦点重定向到子小部件?