0

I have a QGridLayout with a lot of widgets, which I need to resort quite often. I do this, by replacing them one by one in the newly sorted order.

That means, however, that with every added widget the layout wants to update itself and redraw its children, which causes a lot of unnecessary slowdown.

I would like to tell the layout to just not do any layouting or drawing, until I'm done adding everything that I want to add.

I'm trying to do this by overwriting the update() method and only calling the parent method, once I am done adding all the widgets.

    def update(self):
        self._fill_layout()

        super(MyLayout, self).update()

That still does not seem to remove all the slowdown, so I also catch all calls to setGeometry() and only call its parent version 20ms after the last call to it was made.

    def setGeometry(self, screen_rect=None):
        if screen_rect is not None:
            self._last_requested_geometry = screen_rect

            self._delayed_redraw_timer.start(20)
        else:
            # called by our timer
            return super(MyLayout, self).setGeometry(self._last_requested_geometry)

This, however, causes some widgets to be displayed, while they are not part of the layout and just part of the parent widget (I assume, since they are positioned in the top left corner and larger than they should be. So they seem to be painted, while I am still layouting them.

I installed an event filter to the widgets and depending on if they are in a layout or not. But this seems to have the opposite effect, since the widgets are not painted now (more precisely, only the background is). I assume that the paint event, which causes me trouble, is blocked, but no other comes afterwards, so they are put into the layout but not redrawn anymore?

    def eventFilter(self, watched, event) -> bool:
        if event.type() == QEvent.Paint:
            return watched.layout() is None

        return False

I tried fixing this by manually triggering a redraw, with self.parentWidget().repaint() inside the layouts update method, but this does not change anything. The widgets are still not showing.

Is there an easier way to tell the layout to only calculate itself, without updating its visuals and then trigger that paint event manually?

Minix
  • 247
  • 4
  • 17
  • You might want to make use of the [`updatesEnabled`](https://doc.qt.io/qt-5/qwidget.html#updatesEnabled-prop) property of `QWidget`. – G.M. Jan 03 '20 at 12:06
  • @G.M. Of which widget? The one the layout is in? – Minix Jan 03 '20 at 12:08
  • Yes. As per the documentation that should enable/disable the child widgets accordingly. – G.M. Jan 03 '20 at 12:11
  • @G.M. Doesn't change any of the behaviour I'm witnessing, unfortunately. – Minix Jan 03 '20 at 12:18
  • 1
    please provide a [mcve] – S. Nick Jan 03 '20 at 13:01
  • @S.Nick I haven't managed to do that yet, but while trying I found out, that it only happens the first time a widget is set to visible. So it might have something to do with it being polished. I can't imagine that the problem I have is so basic, that I can extract an MRE that deserves the name. If so, I would probably already have found a solution. – Minix Jan 03 '20 at 15:25

1 Answers1

0

I found the solution to my particular problem and as expected it was complicated.

In my widget I have a Gridlayout, which I use to draw basically a table of entries, which can be set to visible or invisible in groups (expanding a sub table).

When new data is added only one row is made visible, the rest of the rows and its widgets are added as "hidden", which makes the layout ignore them, whenever it updates itself.

That means, that, whenever I want to expand a row for the first time, all of its child rows and its widgets will be made visible and the layout will update its state for every widget anew. This could happen a couple dozen times in a row in my case.

To circumvent that, I added a QTimer in the setGeometry() call of my layout class to catch the couple dozen calls in a row and wait for a few milliseconds and resetting the timer with each call. Only when there are no new calls coming in, will I let the setGeometry() call get through and allow the layout to update itself.

This made the layout way more responsive, but now the flickering widgets began. When I expanded a row, I suddenly saw widgets, that were part of new rows appear too large in the top left corner of the main widget for a split second.

At first I thought that meant, that they are shown in the time the QTimer is still active. That they are shown as children of their parent, without being part of the layout yet. That did not turn out to be true, however, they were always part of the layout, but since I added them, when they were invisible, they were not resized or positioned correctly yet and with the QTimer I was delaying that action, while not delaying their paint event. That means they were painted before they were positioned by the layout and then painted again, when that happened, when the timer ran out.

Once I noticed that, I could install an event listener that listened for paint events and disabled them for all widgets at position (0, 0). A crude way to get around the problem, but I'm willing to go there.

That did not solve the problem completely however. I could still see a large empty square appear, where the widget was beforehand. This was noticeable, since the widgets in a row have a different background/window color, than the normal gray.

I thought that this meant, that an empty "cell" of the row was still drawn somehow and that I did not catch it in my event listener. However, after more research I found out, that the background of a window or widget is not drawn in the paint event, but before, seemingly separate from any specific event, that I could catch.

That means the widget I was seeing in the beginning was still there, only not being painted, so that only its background was being displayed.

Since I could not find a way to block the drawing of the widget background I opted to just remove the background color from the widgets, since it was just a slightly lighter shade of gray anyway.

I know this is obviously a sign, that I'm not using the right approach, but sunken-cost-fallacy + no time makes me stick with this for now.

Minix
  • 247
  • 4
  • 17