3
votes

I'm using the Qt framework to build my graphical user interface. I use a QGridLayout to position my QWidgets neatly.
The GUI looks like this:

enter image description here

My application regularly adds new widgets to the GUI at runtime. These new widgets are usually not added at the end of the QLayout, but somewhere in the middle.

The procedure to do this is a bit cumbersome. Applied on the figure above, I would need to take out widg_C, widg_D, ... from the QGridLayout. Next, I add widg_xand widg_y, and finally I put the other widgets back again.
This is how I remove widgets from the QGridLayout:

for i in reversed(range(myGridLayout.count())):
    self.itemAt(i).widget().setParent(None)
###

As long as you're dealing with a small amount of widgets, this procedure is not a disaster. But in my application I display a lot of small widgets - perhaps 50 or more! The application freezes a second while this procedure is ongoing, which is very annoying to the user.
Is there a way to insert widgets somewhere in a QLayout, without the need to take out other widgets?


EDIT: Apparently the solution for the QVBoxLayout is very simple. Just use the function insertWidget(..) instead of addWidget(..). The docs can be found a this link: http://doc.qt.io/qt-5/qboxlayout.html#insertWidget
Unfortunately, I couldn't find a similar function for the QGridLayout.


EDIT: Many people rightly mentioned that putting back a lot of widgets shouldn't cause a performance issue - it is very fast indeed (thank you @ekhumoro to point that out). Apparently, the performance issue I faced had to do with the algorithm putting the widgets back. It is a fairly complicated recursive algorithm that puts every widget on the right coordinates in the QGridLayout. This resulted in a "flicker" on my display. The widgets are taken out, and put back inside with some delay (due to the algorithm) - causing the flicker.


EDIT: I found a solution such that I can easily insert new rows into the QGridLayout. Inserting new rows means that I don't need to take out and replace all the widgets from scratch - hence I avoid the expensive recursive algorithm to run.
The solution can be found in my answer below.

1
Regarding QVBoxLayout, there is the insertWidget method, and some similar questions.BillyJoe
I think you have chosen a wrong model for your GUI. For such dynamic content you might use QTableWidget, for instance. It will take care of all items insertions/deletions etc.vahancho
There must be a problem with your code for inserting a widget, because 50 widgets is a trivial number. I did some testing and it takes less than half a second to insert a widget at the top of a layout containing 2000 widgets and move all the others down.ekhumoro
@ekhumoro I think it depends on how much work is done in the resizeEvent() of the widgets. From memory I seem to recall that Qt generates internal "Layout changed" events whenever you change anything. These end up as calls to resizeEvent() on the widgets (or possibly just calls to moveEvent()). In any case, if you have significant code in those it is going to cause performance problems.Stuart Fisher
@StuartFisher. That all falls under what I meant by: "a problem with your code". The OP needs to provide a minimal reproducible example that demonstrates the behaviour, otherwise the question is moot.ekhumoro

1 Answers

2
votes

Thank you @ekhumoro, @Stuart Fisher, @vahancho and @mbjoe for your help. I eventually found a way to solve the issue. I no longer use the QGridLayout(). Instead, I built a wrapper around the QVBoxLayout to behave as if it was a GridLayout, with an extra function to insert new rows:

class CustomGridLayout(QVBoxLayout):
    def __init__(self):
        super(CustomGridLayout, self).__init__()
        self.setAlignment(Qt.AlignTop)  # !!!
        self.setSpacing(20)


    def addWidget(self, widget, row, col):
        # 1. How many horizontal layouts (rows) are present?
        horLaysNr = self.count()

        # 2. Add rows if necessary
        if row < horLaysNr:
            pass
        else:
            while row >= horLaysNr:
                lyt = QHBoxLayout()
                lyt.setAlignment(Qt.AlignLeft)
                self.addLayout(lyt)
                horLaysNr = self.count()
            ###
        ###

        # 3. Insert the widget at specified column
        self.itemAt(row).insertWidget(col, widget)

    ''''''

    def insertRow(self, row):
        lyt = QHBoxLayout()
        lyt.setAlignment(Qt.AlignLeft)
        self.insertLayout(row, lyt)

    ''''''

    def deleteRow(self, row):
        for j in reversed(range(self.itemAt(row).count())):
            self.itemAt(row).itemAt(j).widget().setParent(None)
        ###
        self.itemAt(row).setParent(None)

    def clear(self):
        for i in reversed(range(self.count())):
            for j in reversed(range(self.itemAt(i).count())):
                self.itemAt(i).itemAt(j).widget().setParent(None)
            ###
        ###
        for i in reversed(range(self.count())):
            self.itemAt(i).setParent(None)
        ###

    ''''''