首页 > 解决方案 > PyQt5 QWidget 事件阻止其他事件。如何避免?

问题描述

我有下一个问题:

A 创建了一个自定义小部件(简单的派生 QWidget 类)。当我用鼠标中键单击它时 - 它会创建另一个 QWidget 类(某种上下文菜单)。当我释放鼠标中键时-该上下文小部件消失了。所以这行得通。不起作用的是 - 上下文小部件还添加了一些内容,如其他小部件、图标等,它们都有自己的自定义事件(简单示例 - enterEvent 和 leaveEvent 带有指示这些事件的打印件)。但是那些内部小部件事件不起作用,当我按住鼠标中键时它们被阻止。当我释放它时 - 上下文小部件消失了。想知道是否有任何解决方案可以让内部小部件的事件按预期工作。

这是一个最小的示例,其中内部小部件不运行鼠标事件,因为它们被 MainWidget 事件阻止:

from PyQt5 import QtWidgets, QtGui, QtCore

class SomeSmallWidget(QtWidgets.QWidget):

    def __init__(self, increment=1, globalValue=0):
        super(SomeSmallWidget, self).__init__()

        # UI
        self.setMinimumWidth(40)
        self.setMaximumWidth(40)
        self.setMinimumHeight(40)
        self.setMaximumHeight(40)

        self.mainLayout = QtWidgets.QVBoxLayout()
        self.setLayout(self.mainLayout)

    def enterEvent(self, event):
        print('Entered')  # NOT WORKING
        super(SomeSmallWidget, self).enterEvent(event)
    
    def leaveEvent(self, event):
        print('Leaved') # NOT WORKING
        super(SomeSmallWidget, self).leaveEvent(event)


class ContextWidget(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super(ContextWidget, self).__init__()

        self.setMouseTracking(True)

        # position
        point = parent.rect().topLeft()
        global_point = parent.mapToGlobal(point)
        self.move(global_point - QtCore.QPoint(0, 0))

        self.innerWidget = SomeSmallWidget() # Here is that small widget, which event does not work

        self.mainLayout = QtWidgets.QVBoxLayout()
        self.setLayout(self.mainLayout)
        self.mainLayout.addWidget(self.innerWidget)
 

class MainWidget(QtWidgets.QLineEdit):

    def __init__(self, value='0'):
        super(MainWidget, self).__init__()
        self.setMouseTracking(True)
        self.popMenu = None

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton: # while we keep MMB pressed - we see context widget
            self.popMenu = ContextWidget(parent=self)
            self.popMenu.show()
        super(MainWidget, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton:
            if self.popMenu:
                self.popMenu = None

标签: pythoneventspyqtpyqt5

解决方案


事件不会被阻止,所有鼠标事件都会发送到按下鼠标按钮时光标下的小部件。通常(总是)它更有意义。想象一下两个相邻的按钮。假设用户按下一个,移动光标并释放第二个按钮。他的意图是什么?他大概改变了主意。如果按钮在鼠标按下时触发动作 - 此选项将不可用并且可能为时过早,如果按钮在鼠标释放时触发动作,哪个按钮应该接收 mouserelease 事件?如果我们将 mouseevent 发送到第二个按钮 - 没有被按下,它将触发使用不想要的操作。如果我们不向第一个按钮发送 mouserelease 事件 - 它将保持下沉模式。想象一下用户在 lineedit 中选择文本,在选择时他离开 lineedit 并转到其他小部件,如果他们以某种方式做出反应,应该切换焦点吗?可能不是。因此,一次只有一个活动窗口和一个焦点小部件,它接收键盘和鼠标输入并对其做出反应。大多数菜单在鼠标释放后显示并在下次鼠标单击时关闭,提供更好的用户体验。

但是,如果您仍然希望您的小部件接收鼠标事件,您可以通过将其转换ContextWidgetSomeSmallWidget这样来实现:

class SomeSmallWidget(QtWidgets.QWidget):
    ...
    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.fillRect(self.rect(), QtCore.Qt.blue)

    def onMouseEnter(self):
        print('onMouseEnter')

    def onMouseLeave(self):
        print('onMouseLeave')

class MainWidget(QtWidgets.QLineEdit):
    ...
    def mouseMoveEvent(self, event):
        if self.popMenu:
            self.popMenu.mouseTest(event.globalPos())

class ContextWidget(QtWidgets.QWidget):

    def __init__(self, parent=None):
        ...
        self._inside = False

    def mouseTest(self, p):
        widget = self.innerWidget
        rect = QtCore.QRect(widget.mapToGlobal(QtCore.QPoint(0,0)), widget.size())
        inside = rect.contains(p)
        if inside != self._inside:
            if inside:
                widget.onMouseEnter()
            else:
                widget.onMouseLeave()
        self._inside = inside

请注意,我添加paintEvent了查看小部件边界。


推荐阅读