带有弹出式 QSlider 的 QToolButton

问题描述

我正在尝试制作音量按钮,点击它应该静音/取消静音,并且在悬停时它应该弹出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 我无法将滑块放在正确的位置。 我不确定 QToolButtonFramelessWindow 是否是实现预期结果的正确小部件,因此如果有更好的方法可以做到这一点,请告诉我。

解决方法

问题不在于 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())
请注意,如果用户在按钮和滑块之间的间隙中移动鼠标,尝试将滑块放置在按钮几何体“外部”可能会导致问题;在这种情况下,您需要创建一个有效的区域来覆盖两个小部件它们之间的合理空间。