Search code examples
pythonpyqt5mousedragqslider

PyQt5: How to detect if QSlider is dragged to the end


I want to detect if a user drags a QSlider handle all the way to the end.

I created a video player that displays the video frames in a QLabel object. A horizontal QSlider object tracks the video position and can also be dragged to position the video. I want the video player to do one thing if the video plays all the way to the end and do something else if the user drags the slider all the way to the end.

I tried detecting if the mouse button is pressed when the slider reaches the end, but have not been able to get that to work in the script below. Is there some other way to determine if the end of the slider is reached when the user drags the slider (mouse button pressed) versus when the video plays to the end by itself (mouse button not pressed)?

from PyQt5 import QtCore, QtWidgets
import sys

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        self.label = QtWidgets.QLabel()
        self.label.setStyleSheet("border: 1px solid black")
        
        self.slider = QtWidgets.QSlider()
        self.slider.setOrientation(QtCore.Qt.Horizontal)

        lay = QtWidgets.QVBoxLayout(central_widget)
        #lay.addWidget(self.label)
        lay.addWidget(self.slider)

        self.resize(640, 480)
        
    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.label.setText('Pressed')
        
    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event) 
        self.label.setText('Released')

    def sliderPressed(self, event):
        super().sliderPressed(event)
        self.label.setText('Pressed')
        
    def sliderReleased(self, event):
        super().sliderReleased(event)
        self.label.setText('Released')

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

Solution

  • The QSlider class inherits QAbstractSlider, which has some signals and functions that make this relatively easy to achieve. In particular, the actionTriggered signal provides fine-grained slider movement notifications, which are independent of any programmatic value changes (i.e. via setValue). This allows tracking all mouse and keyboard changes (via dragging, wheel-scrolling, arrow/page keys, etc) before the value itself changes, which pretty much gives total control. There's also the sliderDown property, which can be used to identify the specific case where the mouse is pressed when dragging.

    Below is a simple demo based on your example:

    from PyQt5 import QtCore, QtWidgets
    import sys
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            central_widget = QtWidgets.QWidget()
            self.setCentralWidget(central_widget)
            self.label = QtWidgets.QLabel()
            self.label.setStyleSheet("border: 1px solid black")
            self.slider = QtWidgets.QSlider()
            self.slider.setOrientation(QtCore.Qt.Horizontal)
            self.slider.setRange(0, 10000)
            self.slider.setSingleStep(50)
            self.slider.setPageStep(500)
            self.slider.actionTriggered.connect(self.handleSliderAction)
            self.button = QtWidgets.QPushButton('Play')
            self.button.clicked.connect(self.handlePlay)
            lay = QtWidgets.QVBoxLayout(central_widget)
            lay.addWidget(self.label)
            lay.addWidget(self.slider)
            lay.addWidget(self.button)
            self.resize(640, 480)
            self.timer = QtCore.QTimer()
            self.timer.setInterval(10)
            self.timer.timeout.connect(
                lambda: self.slider.setValue(self.slider.value() + 1))
    
        def handleSliderAction(self, action):
            value = self.slider.sliderPosition()
            if value == self.slider.maximum():
                if self.slider.isSliderDown():
                    self.label.setText('slider: end (down)')
                else:
                    self.label.setText('slider: end')
            else:
                self.label.setText(f'slider: {value}')
    
        def handlePlay(self):
            if self.timer.isActive():
                self.timer.stop()
                self.button.setText('Play')
            else:
                self.button.setText('Pause')
                self.timer.start()
    
    if __name__ == "__main__":
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())