I'm trying to make volume button, on click it should mute/unmute and and on hover it should popup QSlider
, so user can set whatever level he wants. Now I'm trying to achieve this by showing slider window in enterEvent
and hiding it in 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()
The problem is that mapToGlobal
seems to be connected in some way with enterEvent
and it creates recursion, but without mapToGlobal
I can't place slider at the right position.
I'm not sure that QToolButton
and FramelessWindow
are the right widgets to achieve wished result, so let me know if there a better ways to do that.
The problem is not from
mapToGlobal
, but from the fact that theleaveEvent
is fired as soon as the slider is shown: since the slider is in the same coordinates of the mouse, Qt considers that the mouse has "left" the button (and "entered" the slider).You cannot use the simple leaveEvent for this, as you need to check the cursor position against both the button and the slider.
A possible solution is to create a QRegion that contains the geometry of both widgets and check if the cursor is inside it. In order to process the mouse events of the slider, an event filter must be installed on it:
Note that
self.rect()
is always at coordinates(0, 0)
, so you can just useself.mapToGlobal(QPoint())
to get the widget's global position. On the other hand, you might want to show the slider "outside" the button, so you could useself.mapToGlobal(self.rect().bottomLeft())
.Be aware that trying to place the slider "outside" the button geometry might result in a problem if the user moves the mouse in the gap between the button and the slider; in that case, you will need to create a valid region that covers both widgets and a reasonable space between them.