pythonpyside2qtstylesheetsqscrollbar

How can I resize a QSCrollBar to change it's width in runtime?


I'm currently trying to reproduce the Window 10 Start Menu scroll bar in Qt (python) but I can't figure how to resize my custom QScrollBar to change it's width in runtime.

I tried to resize it using the QScrollBar.resize method (in the enterEvent and leaveEvent) but it scale the widget outside it's "drawing area". For example, my scroll bar is set to a QScrollArea and when I try to resize it, it doesn't take more space and move the widgets, instead of that it just scale on it's right, where I can't see it.

The only solution I've found for now is to use StyleSheet but I can't animate this to have the smooth resize I'm looking for.

There is some code for you to test and see what's wrong:

from PySide2 import QtWidgets, QtCore
from functools import partial

class MyTE(QtWidgets.QPlainTextEdit):
    
    def __init__(self):
        super(MyTE, self).__init__()
        
        self.setVerticalScrollBar(MyScrollBar(self))
        self.setPlainText('mmmmmmmmmmmmmmmmmmmmmmmmmmmmm'*50)
        
class MyScrollBar(QtWidgets.QScrollBar):
    
    def __init__(self, parent=None):
        super(MyScrollBar, self).__init__(parent=parent)
        self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)        
        
    def enterEvent(self, event):
        super(MyScrollBar, self).enterEvent(event)
        
        self.resize(QtCore.QSize(4, self.height()))
        
    def leaveEvent(self, event):
        super(MyScrollBar, self).leaveEvent(event)
        
        self.resize(QtCore.QSize(10, self.height()))
        
wid = MyTE()
wid.show()

Solution

  • .0I've finally found how to do it without using stylesheet ! Thanks eyllanesc for your answer, it give me a new bases to work on, I've finally understand how Qt handle the resizing (I guess), I use the setFixedSize in a method called everytime the value of my animation change.

    To work, this need to have an overrided sizeHint method that return the value of the animation for the width. Also, this works on Autodesk Maya (unfortunately the solution offered by eyllanesc didn't worked in Maya for unknown reason).

    There is my solution:

    from PySide2 import QtWidgets, QtCore, QtGui
    
    
    class MyTE(QtWidgets.QPlainTextEdit):
        def __init__(self):
            super(MyTE, self).__init__()
    
            self.setVerticalScrollBar(MyScrollBar(self))
    
            self.setPlainText("mmmmmmmmmmmmmmmmmmmmmmmmmmmmm" * 50)
            self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
    
        def resizeEvent(self, event):
            super(MyTE, self).resizeEvent(event)
    
    
    class MyScrollBar(QtWidgets.QScrollBar):
        
        def __init__(self, parent=None):
            super(MyScrollBar, self).__init__(parent=parent)
    
            self._animation = QtCore.QVariantAnimation(
                startValue=10.0, endValue=25.0, duration=300
            )
            self._animation.valueChanged.connect(self.changeWidth)
    
            self._width = 10
    
        def enterEvent(self, event):
            super(MyScrollBar, self).enterEvent(event)
            self._animation.setDirection(QtCore.QAbstractAnimation.Forward)
            self._animation.start()
    
        def leaveEvent(self, event):
            super(MyScrollBar, self).leaveEvent(event)
            self._animation.setDirection(QtCore.QAbstractAnimation.Backward)
            self._animation.start()
    
        def sizeHint(self):
            """This must be overrided and return the width processed by the animation
            .. note:: In my final version I've replaced self.height() with 1 because it does not change 
            anything but self.height() was making my widget grow a bit each time.
            """
            return QtCore.QSize(self._width, self.height())
    
        def changeWidth(self, width):
            self._width = width  # This will allow us to return this value as the sizeHint width
            self.setFixedWidth(width) # This will ensure to scale the widget properly.
    
    
    if __name__ == "__main__":
    
        app = QtWidgets.QApplication()
        wid = MyTE()
        wid.show()
        app.exec_()
    

    Note: startValue and endValue of the QVariantAnimation must be float, animation won't work if they are of type int (Work in Qt 5.6.1 (Maya 2018) but not Qt 5.12.5 (Maya 2020)

    PS: If someone is interrested about my final Widget (Window 10 Start menu scroll bar) let ask me in private.