Search code examples
pythonpyside2qscrollarea

How do I create a pyside2 QScrollArea only occupy half the QWidget


I'm trying to make it so that my Window isn't completely occupied by a single QScrollArea.

Here is where I want to put another widget

Basically, I have a Scroll Area, and I want to add something next to it.

I've already seen it created in PySimpleGUI, so I'm sure it can also be created in PySide2, but I'm having trouble creating it. This is where I found the code for the PySimpleGUI. How can I create a column that is scrollable in PySimpleGUI

Scroll Area and Static Area in the same window

So far, I've tried to put the QScrollArea and the QVLayoutBox into a QHLayoutBox, with the addLayout() method, which I thought would allow me to put more widgets next to the QScrollArea, but this just gave me the following error.

self.scroll = QScrollArea()
self.hbox = QHBoxLayout()
self.widget = QWidget()
self.vbox = QVBoxLayout()
self.hbox.addLayout(self.vbox)


RuntimeError: QWidget::setLayout: Attempting to set QLayout "" on QWidget "", when the QLayout already has a parent

I've tried searching it on GitHub for some examples, but everything I found was just people expanding their QScrollArea, and not limiting the Scroll Area to a certain part of the window.

Here is a small piece of code I found online that shows the scroll area, with the code that produces my error added in.

from PySide2.QtWidgets import (QWidget, QSlider, QLineEdit, QLabel, QPushButton, QScrollArea,QApplication,
                             QHBoxLayout, QVBoxLayout, QMainWindow)
from PySide2.QtCore import Qt
from PySide2 import QtWidgets
import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.hbox = QHBoxLayout() # remove this line for the code to work
        self.scroll = QScrollArea()
        self.widget = QWidget()
        self.vbox = QVBoxLayout()
        self.hbox.addLayout(self.vbox) # also this line

        for i in range(1,50):
            object = QLabel("TextLabel")
            self.vbox.addWidget(object)

        self.widget.setLayout(self.vbox)

        self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scroll.setWidgetResizable(True)
        self.scroll.setWidget(self.widget)

        self.setCentralWidget(self.scroll)

        self.setGeometry(600, 100, 1000, 900)
        self.setWindowTitle('Scroll Area Demonstration')
        self.show()

        return

def main():
    app = QtWidgets.QApplication(sys.argv)
    main = MainWindow()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

Without the lines I added in, a scrollbox is created, however, it takes up the entire window, which isn't what I'm trying to create.


Solution

  • You are misunderstanding the usage of layout managers in Qt.

    A layout doesn't do anything on its own, nor it is a overall structure that manages everything: it has to be set on a widget and then it will allow to manage the children of that widget, so it must be created and set within the structure of the widget tree (parent/child relations).

    The general structure is this:

    • QWidget (the parent);
      • QLayout;
        • one or more QWidgets (the children);
        • one or more QLayouts, which in turn should contain further widgets and/or layouts;

    The assumption is that a layout only makes sense when set on a widget, in order to manage at least a child widget.

    In your case, you are also using a QMainWindow, which requires using a "central widget" containing the central part of the window along with other elements (menu bar, status bar, dock widgets and tool bars). QMainWindow has its own private layout, required to manage the above elements, so if you want to show more than one widget in its center, you need a container widget, set a layout for it, and add the real widgets to it.

    The structure you want can be created like this (I'm adding some labels to the scroll area to follow your example image):

    Image of the layout structure

    The solid lines represent actual QWidgets, while the dashed ones are used for layout managers. Note that QMainWindow has its own internal layout as mentioned above, whereas layouts set on a widget use the same color.

    • QMainWindow (purple);
      • QWidget (black): the central widget, used as container;
        • QHBoxLayout, to horizontally show the scroll area and the rest, containing:
          1. QScrollArea (green);
            • QWidget (orange), the container for the widgets that will be scrolled;
              • QVBoxLayout, for the scroll area contents;
                • some QLabels (dark yellow);
          2. QVBoxLayout (blue), note that this is a nested layout;
            • the QLabels (steel blue) that will be shown on the side;
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
    
            central = QWidget()
            self.setCentralWidget(central)
    
            mainLayout = QHBoxLayout()
            central.setLayout(mainLayout)
            # alternatively, you can directly install the layout when creating it
            # hbox = QHBoxLayout(central)
    
            self.scroll = QScrollArea()
            mainLayout.addWidget(self.scroll)
    
            self.scrollContent = QWidget()
            scrollLayout = QVBoxLayout(self.scrollContent)
            for i in range(1, 51):
                scrollLayout.addWidget(QLabel("Scroll label {}".format(i)))
    
            self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
            self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.scroll.setWidgetResizable(True)
            self.scroll.setWidget(self.scrollContent)
    
            vbox = QVBoxLayout()
            mainLayout.addLayout(vbox)
    
            for i in range(1, 11):
                vbox.addWidget(QLabel("Normal label {}".format(i)))
    

    Further notes:

    • object is an important Python keyword, and you shall not use it as a variable name;
    • avoid setting a geometry for the window, as you cannot know the screen sizes and layout other users have; you can eventually call self.resize() (or eventually implement sizeHint()), but you should not use arbitrary and hardcoded values for any of that; at the very least, you should compute a possible geometry through QApplication.primaryScreen();
    • it's good practice to not call show() in the __init__ of a window, as that should only be done externally after the window has been initialized;
    • return at the end of a function is implicit in Python;