QToolButton 与弹出 QSlider

QToolButton with popup QSlider

我正在尝试制作音量按钮,点击它应该 mute/unmute 并且在悬停时它应该弹出 QSlider,因此用户可以设置他想要的任何级别。现在我试图通过在 enterEvent 中显示滑块 window 并将其隐藏在 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()).
请注意,如果用户将鼠标移动到按钮和滑块之间的间隙中,则尝试将滑块放置在按钮几何结构“外部”可能会导致问题;在这种情况下,您将需要创建一个有效区域来覆盖小部件 ,它们之间的合理 space。