QObject::findChild() - "simple" case not working



  • Trying to find a qml item by name from C++ is failing - I have done this in the past, I cannot figure out why this "simple" case doesn't work.

    *main.qml contains an item with objectName: "topographySeries"
    *main.cpp creates QQmlApplicationEngine which loads main.qml
    *main.cpp code then invokes QObject::findChild("topographySeries"), which returns nullptr for some reason

    main.qml snippet:

    import QtQuick 2.0
    import QtQuick.Window 2.12
    import QtDataVisualization 1.2
    
    Window {
        visible: true
        id: mainWindow
        width: 640
        height: 480
        title: qsTr("TopoSeries test")
    
        Item {
            width: 640
            height: 480
    
            Surface3D {
                width: parent.width
                height: parent.height
                Surface3DSeries {
                    id: topographySeries;
                    objectName: "topographySeries"
    

    And here is the main.cpp snippet:

      g_appEngine = new QQmlApplicationEngine();
    
      const QUrl url(QStringLiteral("qrc:/main.qml"));
    
      QObject::connect(g_appEngine, &QQmlApplicationEngine::objectCreated,
                       &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
          QCoreApplication::exit(-1);
      }, Qt::QueuedConnection);
    
      g_appEngine->load(url);
      if (g_appEngine->rootObjects().isEmpty()) {
          qDebug("Empty rootObjects");
          return -1;
        }
    
      // Get root window pointer
      g_rootWindow = qobject_cast<QQuickWindow*>(g_appEngine->rootObjects().value(0));
    
      QObject *object = g_rootWindow->findChild<QObject *>("topographySeries");
      if (!object) {
          qDebug("Couldn't find topographySeries object");
        }
      else {
          qDebug("Found topographySeries object");
        }
    

    qml items are created and displayed, but the C++ code cannot find the "topographySeries" object - and I cannot figure out why. What am I doing wrong?

    Thanks
    Tom


  • Qt Champions 2018

    @tom-asso topographySeries != topographicSeries

    objectName: "topographySeries"
    ...
    QObject *object = g_rootWindow->findChild<QObject *>("topographicSeries");
    


  • @jsulm - thanks very much! That was a clumsy typo in my post, but using the correct name in my actual code still does not find the Surface3DSeries object. Another test - following is a qml that creates several items: Window, Item, Surface3D, Surface3DSeries, and Button - each has a unique objectName. Of these, my C++ code does NOT find the Window and Surface3DSeries objects. Why is that?

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.3
    import QtDataVisualization 1.2
    
    Window {
        objectName: "myWindow"
        visible: true
        width: 640
        height: 480
        id: mainView
    
        Item {
            objectName: "myItem"
            width: parent.width
            height: parent.height
    
            Surface3D {
                objectName: "mySurface"
                width: parent.width
                height: parent.height
    
                Surface3DSeries {
                     objectName: "mySurfaceSeries"
    
                    ItemModelSurfaceDataProxy {
                        itemModel: dataModel
                        rowRole: "longitude"
                        columnRole: "latitude"
                        yPosRole: "elevation"
                    }
                }
            }
            ListModel {
                id: dataModel
                ListElement{ longitude: "20"; latitude: "10"; elevation: "1000"; }
                ListElement{ longitude: "21"; latitude: "10"; elevation: "1200"; }
                ListElement{ longitude: "22"; latitude: "10"; elevation: "900"; }
                ListElement{ longitude: "23"; latitude: "10"; elevation: "1100"; }
                ListElement{ longitude: "20"; latitude: "11"; elevation: "1000"; }
                ListElement{ longitude: "21"; latitude: "11"; elevation: "1300"; }
                ListElement{ longitude: "22"; latitude: "11"; elevation: "1250"; }
                ListElement{ longitude: "23"; latitude: "11"; elevation: "1200"; }
            }
    
            Button {
                objectName: "myButton"
                text: qsTr("my button")
            }
        }
    }
    

    Here is main.cpp snippet, which tries to find each of the object names:

        QObject *root = qobject_cast<QObject *>(engine.rootObjects().value(0));
        char *objectName = "myWindow";
        findObject(objectName, root);
    
        objectName = "myItem";
        findObject(objectName, root);
    
        objectName = "mySurface";
        findObject(objectName, root);
    
        objectName = "mySurfaceSeries";
        findObject(objectName, root);
    
        objectName = "myButton";
        findObject(objectName, root);
    
        return app.exec();
    }
    
    QObject *findObject(char *name, QObject *root) {
        QObject *object = root->findChild<QObject *>(name);
        if (!object) {
            qDebug() << "Couldn't find object " << name;
        }
        else {
            qDebug() << "Found object " << name;
        }
        return object;
    }
    

    Here's the program output:

    Couldn't find object  myWindow
    Found object  myItem
    Found object  mySurface
    Couldn't find object  mySurfaceSeries
    Found object  myButton
    

    Again, thanks for your help.



  • @jsulm - I thought my qml might have some weird problem, but the qmlsurface example provided with Qt seems to have the same issue; C++ code in main.cpp cannot find a Surface3DSeries in main.qml. I slightly modified the example's main.qml, adding objectName "heightSeries" and objectName "surfaceSeries" to the two Surface3DSeries:

               Surface3DSeries {
                    id: surfaceSeries
                    objectName: "surfaceSeries"
                    flatShadingEnabled: false
                    drawMode: Surface3DSeries.DrawSurface
    
                    ItemModelSurfaceDataProxy {
                        //! [5]
                        //! [6]
                        itemModel: surfaceData.model
                        rowRole: "longitude"
                        columnRole: "latitude"
                        yPosRole: "height"
                    }
                    //! [6]
                    onDrawModeChanged: checkState()
                }
                //! [4]
                Surface3DSeries {
                    id: heightSeries
                    objectName: "heightSeries"
                    flatShadingEnabled: false
                    drawMode: Surface3DSeries.DrawSurface
                    visible: false
    

    I slightly modified the example's main.cpp to search for objects named "heightView" and "surfaceView":

       viewer.show();
    
        QObject *root = viewer.rootObject();
        QObject *object = root->findChild<QObject *>("surfaceSeries");
        if (!object) {
            qDebug() << "Could not find surfaceSeries";
        }
        else {
            qDebug() << "Found surfaceSeries";
        }
        
        object = root->findChild<QObject *>("heightSeries");
        if (!object) {
            qDebug() << "Could not find heightSeries";
        }
        else {
            qDebug() << "Found heightSeries";
        }
        return app.exec();
    

    And the above code cannot find "surfaceSeries" or "heightSeries". I must have some fundamental misunderstanding of how the Qt object hierarchy works... :-(


  • Qt Champions 2018

    @tom-asso I'm not sure, but maybe you're searching for the object too early? I mean, if QML loading is asynchronous and you try to search just after load() the QML is maybe not yet completely loaded? Try to use a timer and search after one second or so just to check.



  • @jsulm - added sleep(2) right after engine.load(url) in main.cpp - does not alter the result...
    Anyone able to reproduce my results?
    Thanks!



  • @tom-asso
    When @jsulm says "if QML loading is asynchronous", if that happens in the same process (I assume it does, I don't use QML) your sleep(2) won't help! If you wish to check the issue he suggests I think you do need to set up a QTimer.

    Or better, if I understand right, there is signal https://doc.qt.io/qt-5/qqmlapplicationengine.html#objectCreated for when loading is completed? Ah, I see you have a slot for this, you could try your search from there.



  • @jonb said in QObject::findChild() - "simple" case not working:

    https://doc.qt.io/qt-5/qqmlapplicationengine.html#objectCreated

    @jonB - thanks! I set up a QTimer as advised, but the C++ still does not find objects for Window or Surface3DSeries after several seconds. Both the Window and Surface3DSeries clearly exist, e.g. the Surface3D is displayed by the app.

    main.cpp is searching this root object:

       QObject *root = 
       qobject_cast<QObject *>(engine.rootObjects().value(0));
    

    All qml objects should be children of that root, is that right?



  • Listing all children of the root object, I do not see one of type QSurface3DSeries, even though the qml declares a Surface3DSeries and Surface3D is displayed with the data specified by the series.

    I've modified main.cpp to list names and types of all objects returned by root->findChildren():

        qDebug() << "found " << children.size() << "children";
        for (int i = 0; i < children.size(); i++) {
            qDebug() << "child name " << children[i]->objectName() << ", class " << children[i]->metaObject()->className();
        }
    

    But there is no child of type QSurface3DSeries under root,:

    found  9 children
    child name  "" , class  QQuickRootItem
    child name  "myItem" , class  QQuickItem
    child name  "mySurface" , class  QtDataVisualization::DeclarativeSurface
    child name  "" , class  QQmlListModel
    child name  "myButton" , class  QQuickButton
    child name  "" , class  QQuickRectangle
    child name  "" , class  QQuickPen
    child name  "" , class  QQuickIconLabel
    child name  "label" , class  QQuickMnemonicLabel
    


  • @tom-asso
    I would not know about the "all QML objects being children of the root", but it does not seem unreasonable.

    For the Window/myWindow one, because that's the top-level node maybe that is what engine.rootObjects().value(0) is, and therefore findChild() does not find it because it looks one level down, I don't know.

    If engine.rootObjects() can be multiple objects you could always try searching each of them just in case.

    For the SurfaceSeries/mySurfaceSeries, if it were me I'd just try with a plain findChild<QSurface3DSeries*>(), no name, to find any.

    EDIT
    My reply has crossed with your latest. You are doing the right thing by debug-walking the hierarchy. You need to go down now into "mySurface" and see what is there.



  • Checked to see if mySurface has children and it does not.
    My ultimate goal is to access Surface3DSeries from C++. I'm thinking I could do that by accessing the mySurface object, but my debug shows mySurface is of type QtDataVisualization::DeclarativeSurface, which does not seem to be documented. :-(



  • @tom-asso

    Checked to see if mySurface has children and it does not.

    So the implementation does not just create in the fashion you hoped/expected. They have wrapped it away differently from the declarative.

    My ultimate goal is to access Surface3DSeries from C++.

    You might want to start a new thread for this, as it's very different from current title. Though expert people may see it there anyway.

    QtDataVisualization::DeclarativeSurface, which does not seem to be documented

    And so probably not intended to be used. But you could look at https://code.woboq.org/qt5/qtdatavis3d/src/datavisualizationqml2/declarativesurface.cpp.html, it has functions like QQmlListProperty<QSurface3DSeries> DeclarativeSurface::seriesList() which presumably accesses the series. Use at your own risk.


 

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