Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Layout is changing when using QScrollArea in PyQt5



  • Hello. I am having a problem when using QScrollArea in PyQt5.

    I have attached two widgets to a QStackedWidget in a QScrollArea. I am adding labels to the layouts of the two individual widgets that I am attaching to this QStackedWidget.

    However, when I switch back and forth between the two widgets in my Scroll Area, the layout changes for both widgets. If you continue to press back and forth, the labels continue to "slide" down the second widget.

    I would like the layouts to stay the exact same for each individual layout, for each individual widget in the Scroll Area, and not be modified with each click.

    The following is my code:

    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class FirstWindow(QMainWindow):
        def __init__(self):
            super(FirstWindow, self).__init__()
    
            self.setGeometry(500, 600, 600, 1000)
    
            self.scroll_area = QScrollArea(self)
            self.scroll_area.setGeometry(100, 300, 400, 400)
            self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
            self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.scroll_area.setWidgetResizable(True)
    
            self.push_button = QPushButton(self)
            self.push_button.setGeometry(200, 100, 50, 50)
            self.push_button.setText("Next")
            self.push_button.clicked.connect(self.next)
    
            self.push_button = QPushButton(self)
            self.push_button.setGeometry(200, 150, 50, 50)
            self.push_button.setText("Back")
            self.push_button.clicked.connect(self.back)
    
            self.stacked_widget = QStackedWidget(self)
            self.stacked_widget.setGeometry(100, 300, 398, 398)
    
            self.scroll_area.setWidget(self.stacked_widget)
    
            self.layout_1 = QVBoxLayout()
            self.layout_2 = QVBoxLayout()
    
            self.scroll_widget1 = QWidget(self)
            self.scroll_widget1.setGeometry(100, 300, 398, 398)
    
    
            self.scroll_widget2 = QWidget(self)
            self.scroll_widget2.setGeometry(100, 300, 398, 398)
    
            self.stacked_widget.addWidget(self.scroll_widget1)
            self.stacked_widget.addWidget(self.scroll_widget2)
    
            self.scroll_widget1.setLayout(self.layout_1)
            self.scroll_widget2.setLayout(self.layout_2)
    
            for i in range(50):
                self.label = QLabel(self)
                self.label.setText("FIRST")
                self.layout_1.addWidget(self.label)
    
            for i in range(100):
                self.label = QLabel(self)
                self.label.setText("SECOND")
                self.layout_2.addWidget(self.label)
    
        def next(self):
            self.stacked_widget.setCurrentIndex(1)
            for i in range(70):
                self.label = QLabel(self)
                self.label.setText("FIRST")
                self.layout_1.addWidget(self.label)
    
        def back(self):
            self.stacked_widget.setCurrentIndex(0)
    
    def main():
        app = QApplication(sys.argv)
        win = FirstWindow()
        win.show()
        app.exec_()
    
    main()
    

    Thank you!


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Which version of PyQt5 are you using ?
    On which platform ?

    AFAIR, QStackedWidget will use the size of the its biggest widget for its own size. If you want it to resize to the current one, you will have to manage that yourself.

    Note that you can also add a stretch after the last label to push them all up.



  • I am using PyQt5 version 5.15. I am using PyCharm as my IDE.

    Thank you. How do I resize the current widget? And sorry, I am very new to PyQt, does the stretch help the layout?

    I am thinking instead to use two different QScrollAreas, and show() and hide() both of them in order to accomplish having two different widgets. Is this also acceptable? Thank you


  • Lifetime Qt Champion

    It's up to you but I do not see any issue in using several QScrollArea. That will likely make your coding easier.

    As for the resizing, the idea is to react when the current widget changes and trigger a resize then. However, since you are starting having your big sized widget each in a QScrollArea will be less trouble.

    Since you are using a QMainWindow, you should also set your widget stack as central widget rather than manually placing it.



  • Thanks for your help. Is it better practice to make separate classes for my QScrollAreas? Or can I include this code in my class of QMainWindow?


  • Lifetime Qt Champion

    I'd say it depends on what you are going to develop. For your current tests you can do everything in your QMainWindow.



  • Thanks. For practice, I'm attempting to create a different QScrollArea in a separate class. How would I then attach this separate class to my QMainWindow?


  • Lifetime Qt Champion

    If you implement it in a separate Python file, the same way you do for other widgets, import and use.



  • Okay, I understand. However, how would I accomplish this if I were to add these classes in the same Python file, i.e., in the same main() function as my QMainWindow?

    For example, I would like to pass a variable from my QMainWindow into a separate QScrollArea class. However, I would like the QScrollArea to then be attached to my QMainWindow, which does not happen in my test:

    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class FirstWindow(QMainWindow):
        def __init__(self):
            super(FirstWindow, self).__init__()
    
            self.setGeometry(500, 600, 600, 1000)
    
            self.first = First("Test String")
    
    
    class First(QScrollArea):
        def __init__(self, label_text):
            super(First, self).__init__()
    
            self.setGeometry(100, 300, 400, 400)
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
            self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.setWidgetResizable(True)
    
            self.layout_1 = QVBoxLayout()
    
            self.scroll_widget1 = QWidget(self)
            self.scroll_widget1.setGeometry(100, 300, 398, 398)
    
            self.scroll_widget1.setLayout(self.layout_1)
    
            self.setWidget(self.scroll_widget1)
    
    
            for i in range(50):
                self.label = QLabel(self)
                self.label.setText(label_text)
                self.layout_1.addWidget(self.label)
    
            self.show()
    
    
    def main():
        app = QApplication(sys.argv)
        win = FirstWindow()
        win.show()
        app.exec_()
    
    main()
    

  • Lifetime Qt Champion

    Do not call show in your subclass constructor, it's not its role to say when to be shown.

    I think you are over engineering things. A custom widget that you create should be used in the same manner as any provided by Qt.



  • Thanks! So this example is acceptable/best practice when incorporating other widgets into my QMainWindow?

    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class FirstWindow(QMainWindow):
        def __init__(self):
            super(FirstWindow, self).__init__()
    
            self.setGeometry(500, 600, 600, 1000)
    
            self.scroll = QScrollArea(self)
            self.scroll.setGeometry(100, 300, 400, 400)
            self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
            self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.scroll.setWidgetResizable(True)
    
            self.layout_1 = QVBoxLayout()
    
            self.scroll_widget1 = QWidget(self)
            self.scroll_widget1.setGeometry(100, 300, 398, 398)
    
            self.scroll_widget1.setLayout(self.layout_1)
    
            self.scroll.setWidget(self.scroll_widget1)
    
            for i in range(50):
                self.label = QLabel(self)
                self.label.setText("Test String")
                self.layout_1.addWidget(self.label)
    
    def main():
        app = QApplication(sys.argv)
        win = FirstWindow()
        win.show()
        app.exec_()
    
    main()
    

  • Lifetime Qt Champion

    You forgot to set your QScrollArea as central widget.



  • Thanks for the reminder. Why would you say that this has to be the central widget? Suppose I add a title to the GUI, this would not be the central widget?


  • Lifetime Qt Champion

    Just because you are using a QMainWindow as base class of your main widget. Otherwise, you should put it in a layout in your main widget.


Log in to reply