滚动通过封装 QScrollArea() 时,小部件的 leaveEvent() 被错过
widget's leaveEvent() is missed when scrolling through the encapsulating QScrollArea()
我在 QScrollArea()
中有一个 QWidget()
的网格。我出于特定原因覆盖了他们的 leaveEvent()
(稍后会详细介绍)。当用鼠标指针四处移动时, leaveEvent()
被触发就好了。但是当我保持鼠标指针静止并使用滚轮滚动 QScrollArea()
时,鼠标指针有效地离开了小部件而不会触发 leaveEvent()
.
1。一些上下文
我正在开发一个浏览 Arduino 库的应用程序:
您在屏幕截图中看到的是中间有一个 QScrollArea()
,右侧有一个滚动条,底部有一个滚动条。此 QScrollArea()
包含一个大型 QFrame()
,其中包含其 QGridLayout()
中的所有小部件。每个小部件 - 通常是 QLabel()
或 QPushButton()
- 都有浅灰色边框。这样,我就可以看到他们所在的网格。
2。点亮一整排
单击小部件时,我希望整行都亮起。为此,我为每个人覆盖了 mousePressEvent()
:
注意:实际上,我只是让每个小部件成为自定义 class
的子classes
CellWidget()
,这样我只需要重写一次这个方法。
def mousePressEvent(self, e:QMouseEvent) -> None:
'''
Set 'blue' property True for all neighbors.
'''
for w in self.__neighbors:
w.setProperty("blue", True)
w.style().unpolish(w)
w.style().polish(w)
w.update()
super().mousePressEvent(e)
return
变量self.__neighbors
是每个小部件对其左右两侧所有相邻小部件的引用。这样,每个小部件都可以访问其自己行中的所有小部件。
如您所见,我从每个小部件设置了 属性 "blue"
。这对样式表有影响。例如,这是我给 QLabel()
小部件的样式表:
# Note: 'self' is the QLabel() widget.
self.setStyleSheet(f"""
QLabel {
background-color: #ffffff;
color: #2e3436;
border-left: 1px solid #eeeeec;
border-top: 1px solid #eeeeec;
border-right: 1px solid #d3d7cf;
border-bottom: 1px solid #d3d7cf;
}
QLabel[blue = true] {
background-color: #d5e1f0;
}
""")
unpolish()
和 polish()
方法确保重新加载样式表(我认为?)并且 update()
方法调用重绘。该程序效果很好。我单击任何给定的小部件,整行都亮起蓝色!
3。关闭(蓝色)灯
要关闭它,我覆盖了 leaveEvent()
方法:
def leaveEvent(self, e:QEvent) -> None:
'''
Clear 'blue' property for all neighbors.
'''
for w in self.__neighbors:
w.setProperty("blue", False)
w.style().unpolish(w)
w.style().polish(w)
w.update()
super().leaveEvent(e)
return
所以整行保持点亮,直到您用鼠标指针离开小部件。 leaveEvent()
清除所有邻居的 'blue'
属性 并强制重新绘制它们。
4。问题
但是有一个弱点。当我将鼠标保持在同一位置并向下滚动鼠标滚轮时,鼠标指针有效地离开了小部件而不会触发 leaveEvent()
。缺少这样的 leaveEvent()
会破坏我的解决方案。在保持鼠标不动并向下滚动的同时,您可以单击几行,它们都保持蓝色。
5。系统信息
我正在为 Windows 和 Linux 开发一个应用程序。该应用程序使用 Python 3.8
编写并使用 PyQt5
.
enterEvent
和 leaveEvent
显然都依赖于鼠标的移动,并且由于滚动不涉及任何移动,您的方法只有在用户稍微移动鼠标后才会起作用。
我可以考虑两种可能的解决方法,但它们都有点 hacky 而且不是很优雅。在这两种情况下,我都将滚动区域子类化并覆盖 viewportEvent
轻轻移动鼠标
由于这些事件需要鼠标移动,让我们移动鼠标(然后将其移回):
class ScrollArea(QtWidgets.QScrollArea):
def fakeMouseMove(self):
pos = QtGui.QCursor.pos()
# slightly move the mouse
QtGui.QCursor.setPos(pos + QtCore.QPoint(1, 1))
# ensure that the application correctly processes the mouse movement
QtWidgets.QApplication.processEvents()
# restore the previous position
QtGui.QCursor.setPos(pos)
def viewportEvent(self, event):
if event.type() == event.Wheel:
# let the viewport handle the event correctly, *then* move the mouse
QtCore.QTimer.singleShot(0, self.fakeMouseMove)
return super().viewportEvent(event)
模拟进入和离开事件
这使用 widgetAt()
并创建发送到相关小部件的假进入和离开事件,这对性能来说不是很好:widgetAt
可能很慢,重新抛光小部件也需要一些时间.
class ScrollArea(QtWidgets.QScrollArea):
def viewportEvent(self, event):
if event.type() == event.Wheel:
old = QtWidgets.QApplication.widgetAt(event.globalPos())
res = super().viewportEvent(event)
new = QtWidgets.QApplication.widgetAt(event.globalPos())
if new != old:
QtWidgets.QApplication.postEvent(old,
QtCore.QEvent(QtCore.QEvent.Leave))
QtWidgets.QApplication.postEvent(new,
QtCore.QEvent(QtCore.QEvent.Enter))
return res
return super().viewportEvent(event)
我在 QScrollArea()
中有一个 QWidget()
的网格。我出于特定原因覆盖了他们的 leaveEvent()
(稍后会详细介绍)。当用鼠标指针四处移动时, leaveEvent()
被触发就好了。但是当我保持鼠标指针静止并使用滚轮滚动 QScrollArea()
时,鼠标指针有效地离开了小部件而不会触发 leaveEvent()
.
1。一些上下文
我正在开发一个浏览 Arduino 库的应用程序:
您在屏幕截图中看到的是中间有一个 QScrollArea()
,右侧有一个滚动条,底部有一个滚动条。此 QScrollArea()
包含一个大型 QFrame()
,其中包含其 QGridLayout()
中的所有小部件。每个小部件 - 通常是 QLabel()
或 QPushButton()
- 都有浅灰色边框。这样,我就可以看到他们所在的网格。
2。点亮一整排
单击小部件时,我希望整行都亮起。为此,我为每个人覆盖了 mousePressEvent()
:
注意:实际上,我只是让每个小部件成为自定义 class
的子classes
CellWidget()
,这样我只需要重写一次这个方法。
def mousePressEvent(self, e:QMouseEvent) -> None:
'''
Set 'blue' property True for all neighbors.
'''
for w in self.__neighbors:
w.setProperty("blue", True)
w.style().unpolish(w)
w.style().polish(w)
w.update()
super().mousePressEvent(e)
return
变量self.__neighbors
是每个小部件对其左右两侧所有相邻小部件的引用。这样,每个小部件都可以访问其自己行中的所有小部件。
如您所见,我从每个小部件设置了 属性 "blue"
。这对样式表有影响。例如,这是我给 QLabel()
小部件的样式表:
# Note: 'self' is the QLabel() widget.
self.setStyleSheet(f"""
QLabel {
background-color: #ffffff;
color: #2e3436;
border-left: 1px solid #eeeeec;
border-top: 1px solid #eeeeec;
border-right: 1px solid #d3d7cf;
border-bottom: 1px solid #d3d7cf;
}
QLabel[blue = true] {
background-color: #d5e1f0;
}
""")
unpolish()
和 polish()
方法确保重新加载样式表(我认为?)并且 update()
方法调用重绘。该程序效果很好。我单击任何给定的小部件,整行都亮起蓝色!
3。关闭(蓝色)灯
要关闭它,我覆盖了 leaveEvent()
方法:
def leaveEvent(self, e:QEvent) -> None:
'''
Clear 'blue' property for all neighbors.
'''
for w in self.__neighbors:
w.setProperty("blue", False)
w.style().unpolish(w)
w.style().polish(w)
w.update()
super().leaveEvent(e)
return
所以整行保持点亮,直到您用鼠标指针离开小部件。 leaveEvent()
清除所有邻居的 'blue'
属性 并强制重新绘制它们。
4。问题
但是有一个弱点。当我将鼠标保持在同一位置并向下滚动鼠标滚轮时,鼠标指针有效地离开了小部件而不会触发 leaveEvent()
。缺少这样的 leaveEvent()
会破坏我的解决方案。在保持鼠标不动并向下滚动的同时,您可以单击几行,它们都保持蓝色。
5。系统信息
我正在为 Windows 和 Linux 开发一个应用程序。该应用程序使用 Python 3.8
编写并使用 PyQt5
.
enterEvent
和 leaveEvent
显然都依赖于鼠标的移动,并且由于滚动不涉及任何移动,您的方法只有在用户稍微移动鼠标后才会起作用。
我可以考虑两种可能的解决方法,但它们都有点 hacky 而且不是很优雅。在这两种情况下,我都将滚动区域子类化并覆盖 viewportEvent
轻轻移动鼠标
由于这些事件需要鼠标移动,让我们移动鼠标(然后将其移回):
class ScrollArea(QtWidgets.QScrollArea):
def fakeMouseMove(self):
pos = QtGui.QCursor.pos()
# slightly move the mouse
QtGui.QCursor.setPos(pos + QtCore.QPoint(1, 1))
# ensure that the application correctly processes the mouse movement
QtWidgets.QApplication.processEvents()
# restore the previous position
QtGui.QCursor.setPos(pos)
def viewportEvent(self, event):
if event.type() == event.Wheel:
# let the viewport handle the event correctly, *then* move the mouse
QtCore.QTimer.singleShot(0, self.fakeMouseMove)
return super().viewportEvent(event)
模拟进入和离开事件
这使用 widgetAt()
并创建发送到相关小部件的假进入和离开事件,这对性能来说不是很好:widgetAt
可能很慢,重新抛光小部件也需要一些时间.
class ScrollArea(QtWidgets.QScrollArea):
def viewportEvent(self, event):
if event.type() == event.Wheel:
old = QtWidgets.QApplication.widgetAt(event.globalPos())
res = super().viewportEvent(event)
new = QtWidgets.QApplication.widgetAt(event.globalPos())
if new != old:
QtWidgets.QApplication.postEvent(old,
QtCore.QEvent(QtCore.QEvent.Leave))
QtWidgets.QApplication.postEvent(new,
QtCore.QEvent(QtCore.QEvent.Enter))
return res
return super().viewportEvent(event)