python - 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
解决方案
事件不会被阻止,所有鼠标事件都会发送到按下鼠标按钮时光标下的小部件。通常(总是)它更有意义。想象一下两个相邻的按钮。假设用户按下一个,移动光标并释放第二个按钮。他的意图是什么?他大概改变了主意。如果按钮在鼠标按下时触发动作 - 此选项将不可用并且可能为时过早,如果按钮在鼠标释放时触发动作,哪个按钮应该接收 mouserelease 事件?如果我们将 mouseevent 发送到第二个按钮 - 没有被按下,它将触发使用不想要的操作。如果我们不向第一个按钮发送 mouserelease 事件 - 它将保持下沉模式。想象一下用户在 lineedit 中选择文本,在选择时他离开 lineedit 并转到其他小部件,如果他们以某种方式做出反应,应该切换焦点吗?可能不是。因此,一次只有一个活动窗口和一个焦点小部件,它接收键盘和鼠标输入并对其做出反应。大多数菜单在鼠标释放后显示并在下次鼠标单击时关闭,提供更好的用户体验。
但是,如果您仍然希望您的小部件接收鼠标事件,您可以通过将其转换ContextWidget
为SomeSmallWidget
这样来实现:
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
了查看小部件边界。
推荐阅读
- php - 生成顺序唯一码,经常出现重复输入错误
- ruby - 如何在 Ruby 中使用 Post 请求发送 XML 文件
- c# - SELECT * FROM USERS where userid = (null or '') 这个 t sql 查询是否有效?
- ios - Xcode 11 Beta - Storyboard ViewControllers 出现黑色
- java - X 不是抽象的,不会覆盖抽象方法
- c# - 将控制对象传递给基本构造函数
- python - 从每月索引 Python 返回一列“月中的天数”
- javascript - 如何将百里香字符串变量传递给javascript参数?
- java - org.jboss.redhat-fuse/fabric8-maven-plugin 和 io.fabric8/fabric8-maven-plugin 之间的区别
- c++ - 如何将多个 QStringLists 中的项目添加到一个?