Search code examples
pythonpyqtautoresizeqvboxlayoutqboxlayout

How to modify single column QVBoxLayout to be multi-column?


I have a column of auto-generated buttons which, if there are too many of, can squash UI elements in the window. Therefore, I want to automatically convert the single column of buttons - nominally inside of a QVBoxLayout referred to as self.main_layout - into a multi-column affair by:

  • Removing the buttons from self.main_layout
  • Adding them to alternating new columns represented by QVBoxLayouts
  • Changing self.main_layout to a QHBoxLayout
  • Adding the new columns to this layout

My attempt simply results in the buttons staying in a single column but now don't even resize to fill the QSplitter frame they occupy:

app = QApplication(sys.argv)
window = TestCase()
app.exec_()

class TestCase(QMainWindow):
    def __init__(self):
        super().__init__()
        test = QWidget()
        self.layout = QVBoxLayout()
        test.setLayout(self.layout)
        for i in range(10):
            temp_btn = QPushButton(str(i))
            temp_btn.pressed.connect(self.multi_col)
            self.layout.addWidget(temp_btn)
        self.setCentralWidget(test)

    @pyqtSlot()
    def multi_col(self):
        cols = [QVBoxLayout(), QVBoxLayout()]
        while self.layout.count():
            child = self.layout.takeAt(0)
            if child.widget():
                self.layout.removeItem(child)
                cols[0].addItem(child)
                cols[1], cols[0] = cols[0], cols[1]
        self.layout = QHBoxLayout()
        self.layout.addLayout(cols[0])
        self.layout.addLayout(cols[1])

Any glaringly obvious thing I'm doing wrong here?


Solution

  • Replacing a layout of a QWidget is not so simple with assigning another object to the variable that stored the reference of the other layout. In a few lines of code you are doing:

    self.layout = Foo()
    widget.setLayout(self.layout)
    self.layout = Bar()
    

    An object is not the same as a variable, the object itself is the entity that performs the actions but the variable is only a place where the reference of the object is stored. For example, objects could be people and variables our names, so if they change our name it does not imply that they change us as a person.

    The solution is to remove the QLayout using sip.delete and then set the new layout:

    import sys
    
    from PyQt5.QtCore import pyqtSlot
    from PyQt5.QtWidgets import (
        QApplication,
        QHBoxLayout,
        QMainWindow,
        QPushButton,
        QVBoxLayout,
        QWidget,
    )
    import sip
    
    
    class TestCase(QMainWindow):
        def __init__(self):
            super().__init__()
            test = QWidget()
            self.setCentralWidget(test)
    
            layout = QVBoxLayout(test)
            for i in range(10):
                temp_btn = QPushButton(str(i))
                temp_btn.pressed.connect(self.multi_col)
                layout.addWidget(temp_btn)
    
        @pyqtSlot()
        def multi_col(self):
            cols = [QVBoxLayout(), QVBoxLayout()]
            old_layout = self.centralWidget().layout()
    
            while old_layout.count():
                child = old_layout.takeAt(0)
                widget = child.widget()
                if widget is not None:
                    old_layout.removeItem(child)
                    cols[0].addWidget(widget)
                    cols[1], cols[0] = cols[0], cols[1]
            sip.delete(old_layout)
            lay = QHBoxLayout(self.centralWidget())
            lay.addLayout(cols[0])
            lay.addLayout(cols[1])
    
    
    def main():
        app = QApplication(sys.argv)
        window = TestCase()
        window.show()
        app.exec_()
    
    
    if __name__ == "__main__":
        main()