2

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_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
slalomchip
  • 779
  • 2
  • 9
  • 25

1 Answers1

4

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_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • This is GREAT! I was unaware of the `isSliderDown` method. For me, the script you provided only displayed the slider value when it was being dragged (not when playing by itself) and never displayed the string `'slider: end (down)'`, but the essence of what you provided is exactly what I need! – slalomchip Mar 07 '22 at 02:06
  • @slalomchip That's exactly as intended - the label should never show any changes when playing by itself, since the aim is to distinguish user updates from programmatic updates. Also, for me, the slider-down works perfectly - although it can be a little tricky sometimes when the player is running (which doesn't seem particularly surprising, since it's updated via a timer, and is only intended as a crude mock-up). – ekhumoro Mar 07 '22 at 02:31
  • @slalomchip PS: I amended my script to use a longer slider range and a much shorter timer interval, which should give more realistic behaviour. – ekhumoro Mar 07 '22 at 02:52
  • @slalomchip Always remember to carefully study the documentation, including that of **all** inherited classes. For instance, since you're using QSlider, you need to read the whole docs of: QSlider > [QAbstractSlider](//doc.qt.io/qt-5/qabstractslider.html) > [QWidget](//doc.qt.io/qt-5/qwidget.html) > [QObject](//doc.qt.io/qt-5/qobject.html) (and, for completeness, [QPaintDevice](//doc.qt.io/qt-5/qpaintdevice.html), even if it's a low level class that will be hardly required for common usage). Special attention must be given to all (inherited) signals and properties of any QObject based class. – musicamante Mar 07 '22 at 03:08
  • @ekhumoro Oops - I hastily copied and pasted the wrong thing. I meant to say that the `'slider: end'` never displays when reaching the end without dragging the slider. The `'slider: end (down)'` displays as intended. Regardless, this should work fine. – slalomchip Mar 07 '22 at 14:14
  • @musicamante Agree. I evidently didn't read everything. I focused too much trying to get the QAbstractSlider's `sliderPressed` and `sliderReleased` to solve the issue but overlooked that those are signals and `isSliderDown` holds the state, which is what I really needed. – slalomchip Mar 07 '22 at 14:24