Qiang Zhang
Qiang Zhang

Reputation: 952

PyQT5: how to automatically align the widget in QGridLayout?

I am using QGridLayout in my project. I will automatically add some widgets in QGridLayout. Thus, I implement a class MultiWidgetLayout to manager the widget. My test code is:

import numpy as np
from PyQt5.QtWidgets import *
import sys
class MultiWidgetLayout(QGridLayout):

    def __init__(self):
        super().__init__()
        self.setSpacing(0)
        self.setContentsMargins(1,1,1,1)
        self.__widgets = []

    def getWidgetNum(self):
        return len(self.__widgets)

    def addWidget(self, widget):
        self.__widgets.append(widget)
        self.updateLayout()

    def setWidgets(self, widgets):
        self.__widgets = widgets
        self.updateLayout()

    def updateLayout(self):
        self.removeAllWidgets()
        n = len(self.__widgets)
        columns = int(np.ceil(np.sqrt(n)))
        rows = n//columns

        k = 0
        for r in range(rows):
            for c in range(columns):
                super().addWidget(self.__widgets[k], r, c)
                k += 1
        remain = n - columns*rows
        if remain > 0:
            for r in range(remain):
                super().addWidget(self.__widgets[k], rows, r)
                k += 1

        for widget in self.__widgets:
            widget.show()

    def removeAllWidgets(self):
        '''
        remove all the widgets
        '''
        N = self.count()
        for i in range(N-1,-1,-1):
            item = self.itemAt(i) # item is QHBoxLayout
            self.removeItem(item)
            # self.removeWidget(item)

        for widget in self.__widgets:
            widget.hide()

class Window(QWidget):

    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.multiWidgetLayout = MultiWidgetLayout()
        self.btn = QPushButton('add widget')
        self.btn.clicked.connect(self.addWidgetSlot)
        layout.addWidget(self.btn)
        layout.addLayout(self.multiWidgetLayout)

    def addWidgetSlot(self, check=False):
        num = self.multiWidgetLayout.getWidgetNum()
        label = QLabel(str(num))
        label.setStyleSheet('border: 2px solid red;')
        label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.multiWidgetLayout.addWidget(label)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    app.exec_()

This code basically fullfile what I want, but there still be a small problem.

When I add three widgets, the layout is:

enter image description here

However, I want the QLabel(2) occupy the whole space of the bottom row.

The layout of 5 widgets is:

enter image description here

But, I hope QLable(3) and QLabel(4) occupy the whole space of the bottom row.

Any suggestion is appreciated!

Upvotes: 0

Views: 579

Answers (1)

musicamante
musicamante

Reputation: 48231

Since the widgets are going to have similar sizes, hints and policies, you can add any remaining widgets to a horizontal layout that is placed at the last row, spanning all columns.

Consider that there is no need to remove widgets and add them again, since addWidget() automatically replaces their position in the layout and reparents them whenever required, which also makes unnecessary to call show() on all of them.
This is important, for two reasons:

  1. show() overrides an explicit hide() or setVisible(False), and that shouldn't happen);
  2. removing an item from a layout isn't always effective, since the item's parent (widget or layout) won't change, and could result in unexpected behavior;

Also note that sqrt and ceil are already provided by the math module, which is part of the python standard library. Numpy is quite a big module, and if you don't need that for anything else you're going to add an important requirement which would ask for unnecessary resources (not only in memory, but also including install, if not even actual building).

In the following implementation I used an iter() to cycle through all widgets, so that there's no need for item indexing.

from math import sqrt, ceil
from PyQt5.QtWidgets import *
import sys

class MultiWidgetLayout(QGridLayout):
    def __init__(self):
        super().__init__()
        self.setSpacing(0)
        self.setContentsMargins(1, 1, 1, 1)
        self.__widgets = []
        self.__lastLayout = QHBoxLayout()

    # ...

    def updateLayout(self):
        n = len(self.__widgets)
        columns = ceil(sqrt(n))
        rows, remain = divmod(n, columns)
        widgets = iter(self.__widgets)
        for r in range(rows):
            for c in range(columns):
                super().addWidget(next(widgets), r, c)
        self.removeItem(self.__lastLayout)
        if remain:
            self.addLayout(self.__lastLayout, rows, 0, 1, columns)
            for i in range(remain):
                self.__lastLayout.addWidget(next(widgets))

Upvotes: 1

Related Questions