[Bug?] Inconsistent resizing of QQuickView (unexpected item's width & height)



  • I'm facing this problem with my app: when creating an instance of QQuickView and resizing it, it seems that the resizing is not actually performed after a much later time. Let me explain with a minimal sample code:

    C++ side:
    @
    #include <QGuiApplication>
    #include <QQuickView>
    #include <QQuickItem>
    #include <QUrl>
    #include <QMetaObject>
    #include <QVariant>

    int main(int argc, char *argv[])
    {
    QGuiApplication app(argc, argv);

    QQuickView* viewer = new QQuickView();
    viewer->setSource(QUrl::fromLocalFile&#40;"main.qml"&#41;&#41;;
    viewer->setResizeMode(QQuickView::SizeRootObjectToView&#41;;
    viewer->resize(500, 500&#41;;
    viewer->show(&#41;;
    
    QMetaObject::invokeMethod(viewer->rootObject(),
        "addTile", Q_ARG(QVariant, 3));
    
    return app.exec&#40;&#41;;
    

    }
    @

    QML side:
    @
    import QtQuick 2.0

    Rectangle {
    id: root

    property real gridItemSize: width * 0.2125
    
    function addTile(index&#41; {
        console.log("[QML addTile]"&#41;;
        console.log("width:", width, "height:", height&#41;;
        for (var i = 0; i < gridRepeater.count; i++) {
            console.log("Item", i,
                "x:", gridRepeater.itemAt(i).x,
                "y:", gridRepeater.itemAt(i).y);
        }
    
        var tile = Qt.createQmlObject(
            'import QtQuick 2.0; Rectangle { color: "red" }', root);
        tile.width = gridItemSize;
        tile.height = gridItemSize;
        tile.x = gridRepeater.itemAt(index).x;
        tile.y = gridRepeater.itemAt(index).y;
    }
    
    Grid {
        columns: 2
    
        Repeater {
            id: gridRepeater
            model: 6
    
            Rectangle {
                width: gridItemSize; height: gridItemSize
                border.color: "black"
            }
        }
    }
    
    Component.onCompleted: {
        console.log("[QML onCompleted]");
        console.log("width:", width, "height:", height);
        for (var i = 0; i < gridRepeater.count; i++) {
            console.log("Item", i,
                "x:", gridRepeater.itemAt(i).x,
                "y:", gridRepeater.itemAt(i).y);
        }
    }
    

    }
    @

    Here, the intent is to create the window with a predefined and unknown size (it will be whatever device's screen resolution is). Later, the function "addTile" is called to create a small rectangle, but the result is nothing gets (visually) created because at that point everything is 0: all sizes, positions and the Grid children coordinates, as one can see in the log output:
    @
    [QML onCompleted]
    width: 0 height: 0
    Item 0 x: 0 y: 0
    Item 1 x: 0 y: 0
    Item 2 x: 0 y: 0
    Item 3 x: 0 y: 0
    Item 4 x: 0 y: 0
    Item 5 x: 0 y: 0
    [QML addTile]
    width: 0 height: 0
    Item 0 x: 0 y: 0
    Item 1 x: 0 y: 0
    Item 2 x: 0 y: 0
    Item 3 x: 0 y: 0
    Item 4 x: 0 y: 0
    Item 5 x: 0 y: 0
    @

    I tried with "qApp->processEvents();" before the "QMetaObject::invokeMethod" call, to no avail. What am I missing here?

    EDIT - SOLVED

    I found the solution: the resize is being performed as an event in the application event loop, so in order to let QML recalculate all the values which depend on width & height, we need to let it processEvents() after resizing the QQuickView in the C++ side, just like this:

    @
    QQuickView* viewer = new QQuickView();
    viewer->setSource(QUrl::fromLocalFile("main.qml"));
    viewer->setResizeMode(QQuickView::SizeRootObjectToView);
    viewer->show();
    viewer->resize(500, 500);
    qApp->processEvents();
    // At this point, width, height and positioners already have correct values
    @

    Strangely enough, I'd swear that I already tried this before and it didn't work, but I was at a different machine with a different build of Qt... for now, I'm changing the title of this as "Solved".

    I think the docs are missing a mention to this important fact about the behavior of the QQuickView::resize() method, so I'll file a documentation bug report.



  • EDITED - Merged with first post



  • Hi, you should call functions like your "addTile" only after the QML item has been created. Usually you use "Component.onCompleted" signal for that in QML. DO you have to call that function from c++?

    Edit: sorry just noticed that you did use that signal, weird.

    maybe another tip, I am using the ApplicationWindow QML class, and the window is automatically scaled to the device screen (on mobile devices the width and height is ignored). If you need something like that? I don't know if that behaviors is the same if you use a simple QML Item as the root element, maybe that gets resized in the same way I've never tried.



  • Hi, thanks for the tip, I'll check different ways of initializing the main window next time.

    IMHO the problem I'm facing is that width and height values are being calculated at a different point in time from what I expected; I guessed that the main QML component would have a size of 500x500 after C++ line 15, or maybe 16, in my sample code. But it turns out that it doesn't work that way.

    EDIT - Solved! check my first post.



  • OK this is not "Solved", but it actually might be a bug. Now that I had access to a Linux machine, I could test that the solution works under Windows, but fails under Linux. That explains why I thought that the processEvents() call hadn't worked the first time for me (I didn't remember it was done in a Linux machine).

    So, with current solution, this is the output log when running on Windows:
    @
    [QML onCompleted]
    width: 0 height: 0
    Item 0 x: 0 y: 0
    Item 1 x: 0 y: 0
    Item 2 x: 0 y: 0
    Item 3 x: 0 y: 0
    Item 4 x: 0 y: 0
    Item 5 x: 0 y: 0
    [QML addTile]
    width: 500 height: 500
    Item 0 x: 0 y: 0
    Item 1 x: 106.25 y: 0
    Item 2 x: 0 y: 106.25
    Item 3 x: 106.25 y: 106.25
    Item 4 x: 0 y: 212.5
    Item 5 x: 106.25 y: 212.5
    @

    And this is the same program running on Linux:
    @
    [QML onCompleted]
    width: 0 height: 0
    Item 0 x: 0 y: 0
    Item 1 x: 0 y: 0
    Item 2 x: 0 y: 0
    Item 3 x: 0 y: 0
    Item 4 x: 0 y: 0
    Item 5 x: 0 y: 0
    [QML addTile]
    width: 0 height: 0
    Item 0 x: 0 y: 0
    Item 1 x: 0 y: 0
    Item 2 x: 0 y: 0
    Item 3 x: 0 y: 0
    Item 4 x: 0 y: 0
    Item 5 x: 0 y: 0
    @

    This is the platform information:

    • Windows 7 Service Pack 1 64bit; Visual Studio 2012 Update 4; Qt 5.2.1
    • Kubuntu 12.04.4; GCC v4.6.3; Qt 5.2.1


  • Maybe a stupid question but did you try setting the size of the root rectangle in QML or why are you doing that from c++ anyways? All I know is when you do it directely in QML it has to to be applied in the "Component.onCompleted" event. I am not sure about the resize function from c++ it should be synchronous, but the QML renderer might be delayed that is why you have to do "processEvents()" before it is applied to QML, just an idea.
    Actually I never had problems like that when just using QML to trigger a c++ slot from "Component.onCompleted" always works in my apps!? I never used QQuickView directly though, only the generared class QtQuick2ApplicationViewer from Qt Creator QMl project or a custom QQmlApplicationEngine with an ApplicationWindow in the QML main file.



  • Well yeah, if I manually set a specific and arbitrary width and height to the root item, then everything gets those values. But the point is that I don't know beforehand WHICH are the correct values, because every screen is different.

    The point here is that the final code would call QQuickView::showFullScreen(), which is by the way just what is done by the custom "QtQuick2ApplicationViewer" class which gets auto-generated whenever starting a new QtQuick project (so this is an expected use case).



  • Maybe you can just use a different approach to your problem then? If this a bug or not there are other way. For example why don't you make use of QML property binding and property change notification?
    Also you are creating a rectangle from a string, why? (you can do that of course but there are better ways and especially in your case your ca use the repeater delegate)

    As far as I understand you don't set the root size in QML because it is not scaled to the screen size, I don't get it why you need c++ for this case you can use Screen QML class or something but I don't know what your goal is exactly.

    I hope that helps a little, I'm just trying to come up with a solution here :D



  • This is the expected behaviour. If you want to have items which follow the window size then you must use bindings, not imperatively set the properties[1]. If you want to have items which follow the screen size, then bind[2] to the Screen attached property http://qt-project.org/doc/qt-5/qml-qtquick-window-screen.html instead of waiting for the window to get it's final size (a point in time which varies per platform).

    [1]This will be easier if you use a QQmlApplicationEngine instead of a QQuickView and have the window constructed in QML, e.g. Window { visibility: Window.FullScreen; visible: true }
    [2]You may still have to bind to the screen values, as the items don't know which screen they're on until after a certain point in the window setup still. But I think that they'll know much earlier.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.