问题描述
我正在尝试制作音量按钮,点击它应该静音/取消静音,并且在悬停时它应该弹出QSlider
,因此用户可以设置他想要的任何级别。现在我试图通过在 enterEvent
中显示滑块窗口并将其隐藏在 leaveEvent
中来实现这一点:
class VolumeButton(QToolButton):
def __init__(self,parent=None):
super().__init__(parent)
self.setIcon(volumeicon)
self.slider = QSlider()
self.slider.setwindowFlags(Qt.FramelessWindowHint)
self.slider.setwindowModality(Qt.NonModal)
def enterEvent(self,event):
self.slider.move(self.mapToGlobal(self.rect().topLeft()))
self.slider.show()
def leaveEvent(self,event):
self.slider.hide()
问题是 mapToGlobal
似乎以某种方式与 enterEvent
连接并且它创建递归,但是没有 mapToGlobal
我无法将滑块放在正确的位置。
我不确定 QToolButton
和 FramelessWindow
是否是实现预期结果的正确小部件,因此如果有更好的方法可以做到这一点,请告诉我。
解决方法
问题不在于 mapToGlobal
,而是因为 leaveEvent
会在滑块显示后立即触发:由于滑块位于鼠标的相同坐标中,Qt 认为鼠标“离开”了按钮(并“进入”了滑块)。
您不能为此使用简单的 leaveEvent,因为您需要针对按钮和滑块检查光标位置。
一个可能的解决方案是创建一个包含两个小部件的几何图形的 QRegion,并检查光标是否在其中。为了处理滑块的鼠标事件,必须在其上安装事件过滤器:
class VolumeButton(QToolButton):
def __init__(self,parent=None):
super().__init__(parent)
self.setIcon(volumeicon)
self.slider = QSlider()
self.slider.setWindowFlags(Qt.FramelessWindowHint)
self.slider.setWindowModality(Qt.NonModal)
self.slider.installEventFilter(self)
def isInside(self):
buttonRect = self.rect().translated(self.mapToGlobal(QPoint()))
if not self.slider.isVisible():
return QCursor.pos() in buttonRect
region = QRegion(buttonRect)
region |= QRegion(self.slider.geometry())
return region.contains(QCursor.pos())
def enterEvent(self,event):
if not self.slider.isVisible():
self.slider.move(self.mapToGlobal(QPoint()))
self.slider.show()
def leaveEvent(self,event):
if not self.isInside():
self.slider.hide()
def eventFilter(self,source,event):
if source == self.slider and event.type() == event.Leave:
if not self.isInside():
self.slider.hide()
return super().eventFilter(source,event)
请注意,self.rect()
始终位于坐标 (0,0)
,因此您只需使用 self.mapToGlobal(QPoint())
即可获取小部件的全局位置。另一方面,您可能希望在按钮“外部”显示滑块,因此您可以使用 self.mapToGlobal(self.rect().bottomLeft())
。
请注意,如果用户在按钮和滑块之间的间隙中移动鼠标,尝试将滑块放置在按钮几何体“外部”可能会导致问题;在这种情况下,您需要创建一个有效的区域来覆盖两个小部件和它们之间的合理空间。