Search code examples
c++qtqwidgetqgraphicsview

QT: Custom QWidget constructors behavior wrong before show() is called


I have created a custom implementation of a QMainWindow holding a custom QGraphicsView. The View is able to be given and display an image in the background with zoom behavior, which maximizes this image in the view after being loaded while keeping aspect ratio. This behavior works well when I load an Image while the Window is already shown.

I want to give the window a default_image in the constructor, with the intention it being loaded up immediately. I have a function/slot to load this default image in the window, to be called again later. I use this function to also do this initialization in the windows constructor.

When I call the function in the windows or views constructor operations of setting the sceneRect and such do not apply correctly. The image is shown as zoomed extremely far out as if some calls to fitInView and setSceneRect were just ignored. When I call the function after QMainWindow::show() has been called on the window the first time in my main (which is after the constructor is called, obviously), then everything works as intended.

So why does the class have different behavior when being invisible, aka. not being show()n? I can assume from this behavior that a certain class of operation is not applied to widgets when they are not shown yet. How to recognize what applies and what does not? And how to do proper initialization in this case at any rate?

I'd love to refrain from documenting: "You just have to manually call loadImageDefault() after show() has been called!" for my users to have to account for this. It's a default, it should be able to be applied directly in the constructor.

Calling update() on the widgets at different levels in the constructor afterwards has not rendered any improvement.


Solution

  • So it seems geometry is not fully or not set up in the background until a widget is show()n. This will lead to problems for widgets at construction time, if construction involves geometry, like QGraphicsView. Hence setting an image and zoom behavior, etc. will not ever work before show() has been called.

    A way around this is automatic delayed initialization suggested by the docs. Docs suggest the use of QEvent::Polish here, but this did not work for me either. Next option is to override showEvent(QShowEvent*).

    void Window::showEvent(QShowEvent* e)
    {
        QMainWindow::showEvent(e);
    
        if (load_default_image_on_show && !default_image.isNull())
        {
            loadImageDefault();
            load_default_image_on_polish = false;
        }
    }
    

    QMainWindow::showEvent() is called before delayed initialization is handled, because I assume it contributes to the setup of geometry. I am using a bool load_default_image_on_show = true as a lazy trigger. I do not particularly like having a one-use bool, because it expands the window class' definition, but I haven't found anything to test a widget if it has never been shown before, which could replace it. One can test for visibility, but that property can change repeatedly, so one needs the switch instead that only triggers it once.

    This then works as intended and without having the user call anything additional to the constructor for initialization to be correct.