Search code examples
pythonpyqtpyqt5qscrollbar

Getting the size/width of a scrollbar thumb in PyQt5?


Say, you have a PyQt5 scrollbar:

pyqt5scrollbar

I would like to programmatically find the width of the scrollbar "thumb" (edit: called "slider" in http://pyqt.sourceforge.net/Docs/PyQt4/qscrollbar.html), as well as its location; I guess pixels would be the "natural" units for this at first.

From the image above, I'd guess start is always 0 - but can I, and if so how, get the values for left, right, width (although, if we have left and right, width is trivially right-left); and end in pixels?

I'm aware there exist QScrollBar's .value(), pageStep() and singleStep(), but I cannot really tell if they relate to the above dimensions in pixels.

( If a PyQt5 example is needed with scrollbars, see for instance the code I posted in Have tabs keep focus on mousewheel over them in a PyQt5 scrollarea? )


Solution

  • In this case, you must use the subControlRect() method of QStyle() to obtain the rectangles from which you can obtain the information you want.

    In the following example I have created a custom class of QScrollBar that emits that information through a signal every time the position of the slider changes or changes the size of the QScrollBar.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class Scrollbar(QtWidgets.QScrollBar):
        geometryChanged = QtCore.pyqtSignal(int, int, int, int, int)
    
        def __init__(self, parent=None):
            super(Scrollbar, self).__init__(parent)
            self.sliderMoved.connect(self.calculate_geometry)
    
        def resizeEvent(self, event):
            self.calculate_geometry()
            super(Scrollbar, self).resizeEvent(event)
    
        def calculate_geometry(self):
            opt = QtWidgets.QStyleOptionSlider()
            self.initStyleOption(opt)
            gr = self.style().subControlRect(QtWidgets.QStyle.CC_ScrollBar, opt,
                QtWidgets.QStyle.SC_ScrollBarGroove, self)
            sr = self.style().subControlRect(QtWidgets.QStyle.CC_ScrollBar, opt,
                QtWidgets.QStyle.SC_ScrollBarSlider, self)
    
            start = gr.left() if self.orientation() == QtCore.Qt.Horizontal else gr.top()
            left = sr.left() if self.orientation() == QtCore.Qt.Horizontal else sr.top()
            right = sr.right() if self.orientation() == QtCore.Qt.Horizontal else sr.bottom()
            width = sr.width() if self.orientation() == QtCore.Qt.Horizontal else sr.height()
            end = gr.right() if self.orientation() == QtCore.Qt.Horizontal else gr.bottom()
            self.geometryChanged.emit(start, left, width, right, end)
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
    
            hlayv = QtWidgets.QHBoxLayout()
            hlayv.addWidget(QtWidgets.QLabel("start: "))
            self._start_label_v = QtWidgets.QLabel()
            hlayv.addWidget(self._start_label_v)
            hlayv.addWidget(QtWidgets.QLabel("left: "))
            self._left_label_v = QtWidgets.QLabel()
            hlayv.addWidget(self._left_label_v)
            hlayv.addWidget(QtWidgets.QLabel("width: "))
            self._width_label_v = QtWidgets.QLabel()
            hlayv.addWidget(self._width_label_v)
            hlayv.addWidget(QtWidgets.QLabel("right: "))
            self._right_label_v = QtWidgets.QLabel()
            hlayv.addWidget(self._right_label_v)
            hlayv.addWidget(QtWidgets.QLabel("end: "))
            self._end_label_v = QtWidgets.QLabel()
            hlayv.addWidget(self._end_label_v)
    
            hlayh = QtWidgets.QHBoxLayout()
            hlayh.addWidget(QtWidgets.QLabel("start: "))
            self._start_label_h = QtWidgets.QLabel()
            hlayh.addWidget(self._start_label_h)
            hlayh.addWidget(QtWidgets.QLabel("left: "))
            self._left_label_h = QtWidgets.QLabel()
            hlayh.addWidget(self._left_label_h)
            hlayh.addWidget(QtWidgets.QLabel("width: "))
            self._width_label_h = QtWidgets.QLabel()
            hlayh.addWidget(self._width_label_h)
            hlayh.addWidget(QtWidgets.QLabel("right: "))
            self._right_label_h = QtWidgets.QLabel()
            hlayh.addWidget(self._right_label_h)
            hlayh.addWidget(QtWidgets.QLabel("end: "))
            self._end_label_h = QtWidgets.QLabel()
            hlayh.addWidget(self._end_label_h)
    
            self.scrollarea = QtWidgets.QScrollArea()
            content_widget = QtWidgets.QLabel()
            content_widget.setStyleSheet('''background-color : red;''')
            content_widget.setFixedSize(1000, 1000)
            self.scrollarea.setWidget(content_widget)
            hscrollbar = Scrollbar()
            hscrollbar.geometryChanged.connect(self.on_vertical_geometryChanged)
            self.scrollarea.setHorizontalScrollBar(hscrollbar)
            self.scrollarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
    
            vscrollbar =Scrollbar()
            self.scrollarea.setVerticalScrollBar(vscrollbar)
            vscrollbar.geometryChanged.connect(self.on_horizontal_geometryChanged)
            self.scrollarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
    
            vlay = QtWidgets.QVBoxLayout(self)
            vlay.addWidget(QtWidgets.QLabel("<b>Vertical: </b>"))
            vlay.addLayout(hlayv)
            vlay.addWidget(QtWidgets.QLabel("<b>Horizontal: </b>"))
            vlay.addLayout(hlayh)
            vlay.addWidget(self.scrollarea)
    
        @QtCore.pyqtSlot(int, int, int, int, int)
        def on_vertical_geometryChanged(self, start, left, width, right, end):
            self._start_label_v.setNum(start)
            self._left_label_v.setNum(left)
            self._width_label_v.setNum(width)
            self._right_label_v.setNum(right)
            self._end_label_v.setNum(end)
    
        QtCore.pyqtSlot(int, int, int, int, int)
        def on_horizontal_geometryChanged(self, start, left, width, right, end):
            self._start_label_h.setNum(start)
            self._left_label_h.setNum(left)
            self._width_label_h.setNum(width)
            self._right_label_h.setNum(right)
            self._end_label_h.setNum(end)
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())