Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to add a property to a QML object after creation but before binding evaluation?



  • Let's assume the following QML document:

    Project {
        Testcase {
            name: "test1"
            Exports {
                property string result
            }
    
            run: {
                result = "yes"
            }
        }
    
        Testcase {
            name: "test2"
            Depends { name: "test1" }
            property someProperty: test1.result
    
            /* ... */
    }
    

    I am trying to attach the Exports item from the first Testcase object to the second Testcase object under the name test1 so that the binding of someProperty becomes valid. Note: I am explicitly NOT using id:test1 because in my DSL the Testcase objects could be defined in separate documents. The idea is a bit similar to Product items in Qbs, but the meaning and implementation is very different. And again: This is not a normal QML application. This document is analysed and heavily manipulated before it is executed by QQmlEngine.

    I thought I'd have the possibility to do add properties to objects after Component::beginCreate(), but that doesn't work:

    • Calling QObject::setProperty("test1", someObject) on test2 has no effect, even before
      QQmlComponent::completeCreate().
    • Using QQmlProperty on test2 has no effect, probably because the property
      doesn't physically exist.
    • Setting test1 as context property of qmlContext(test2) fails because the context is private. Setting it on the parent context of test2 would bring it into the root context which gives it wrong scope. I want test1 to point to my object only within the scope of test2.

    So I wonder:

    1. Is there any possibility to add a property to an object after QQmlComponent::beginCreate() and before bindings are evaluated in QQmlComponent::completeCreate() ?

    2. Why does every QML object have a private context that is not writable? I've asked this question already here, but didn't get any response.

    I am looking forward for hints and answers.
    Richard



  • @rweickelt said in How to add a property to a QML object after creation but before binding evaluation?:

    test1.result

    Where is test1 defined? If you think it is this:

    Testcase {
        name: "test1"
        ...
    }
    

    then you are mistaken. If you declare it like:

    Testcase {
        id: test1
        name: "test1"
        property string someStringProperty
        ...
    }
    

    Then you can use

    test1.someStringProperty
    


  • Thank you for your reply. I think my question was not clear enough. This is not a normal QML application. Instead, the document is analysed and manipulated before the QML engine executes it.

    The reason why I cannot use id:test1 is that Testcase objects might be defined in separate documents. My backend analyses all documents and based on the Depends items and the Exports items, it creates relationships.

    So if any test case contains an item Depends { name: "bla" } AND the test case "bla" contains an Exports { } item, then this Exports item of "bla" is attached to the dependent test case using the name "bla".

    Does that make it clear?



  • @rweickelt
    OK... my bad. I never used TestCases and the Qbs framework, hence my confusion. Yet another thing for me to learn...



  • Never mind. It's still QML after all and in difference to Qbs I am using a vanilla QQmlEngine. I am just analyzing and manipulating the object tree between QQmlComponent::beginCreate() and QQmlComponent::completeCreate().

    Still looking for a solution to my problem.



  • Here is a minimal almost working example that shows the same issue:

    file.qml:

    import QtQml 2.0
    
    QtObject {
        property var test1: QtObject {
            objectName: "child1"
            property string color: environment.color
        }
    
        property var test2: QtObject {
            objectName: "child2"
            property string color: environment.color
        }
    }
    

    main.cpp:

    #include <QtCore>
    #include <QtQml>
    
    int main(int argc, char* argv[]) {
        QCoreApplication app(argc, argv);
        QQmlEngine engine;
        QQmlComponent component(&engine,QUrl::fromLocalFile("file.qml"));
        QObject* object = component.beginCreate(engine.rootContext());
        QObject* child1 = object->findChild<QObject*>("child1", Qt::FindChildrenRecursively);
        QObject* child2 = object->findChild<QObject*>("child2", Qt::FindChildrenRecursively);
    
        QQmlPropertyMap env1;
        QQmlPropertyMap env2;
        env1["color"] = "green";
        env2["color"] = "blue";
    
        // Now I want to make object1 available for child1
        // and object2 available for child2.
        // But I can't do this:
        //    qmlContext(test1)->setContextProperty("environment", env1);
        //    qmlContext(test2)->setContextProperty("environment", env2);
    
        component.completeCreate();
    
        if (!component.errors().isEmpty()) {
            qDebug() << component.errorString();
        }
    
        Q_ASSERT(child1->property("color") == QString("green"));
        Q_ASSERT(child2->property("color") == QString("blue"));
    
        qDebug() << "It works";
    }
    

    That might be more similar to a normal QML application.



  • What about something like:

    #include <QtCore>
    #include <QtQml>
    #include <QQmlContext>
    #include <QVariant>
    #include <QStringList>
    
    int main(int argc, char* argv[]) {
        QCoreApplication app(argc, argv);
        QQmlEngine engine;
        QQmlComponent component(&engine,QUrl::fromLocalFile("G:/UMAGI_Source/Qt/test6/file.qml"));
        QObject* object = component.beginCreate(engine.rootContext());
        QObject* child1 = object->findChild<QObject*>("child1", Qt::FindChildrenRecursively);
        QObject* child2 = object->findChild<QObject*>("child2", Qt::FindChildrenRecursively);
    
        QQmlPropertyMap env;
        env["colors"] = QVariant(QStringList({"green", "blue"}));
    
        QQmlContext *ctxt = engine.rootContext();
        ctxt->setContextProperty("environment", &env);
    
        component.completeCreate();
    
        if (!component.errors().isEmpty()) {
            qDebug() << component.errorString();
        }
    
        Q_ASSERT(child1->property("color") == QString("green"));
        Q_ASSERT(child2->property("color") == QString("blue"));
    
        qDebug() << "It works";
    }
    

    and

    import QtQml 2.11
    
    QtObject {
        property var test1: QtObject {
            objectName: "child1"
            property string color: environment.colors[0]
        }
    
        property var test2: QtObject {
            objectName: "child2"
            property string color: environment.colors[1]
        }
    }
    
    

    This gives:

    04:08:38: Starting G:\Source\Qt\build-test6-Desktop_Qt_5_11_0_MSVC2017_64bit3-Debug\debug\test6...
    QML debugging is enabled. Only use this in a safe environment.
    It works



  • Indeed. Thank you for taking the time. But the person who writes the documents is not supposed to know the index of the correct element. The env objects in the above example are not randomly assigned.

    What I now ended up with, is a QQmlPropertyMap property in my Testcase item under the name "dependencies". Speaking in the terms of my original post: For each Depends item, I am now inserting a QVariantMap that represents the Exports item. That gives me unnecessary verbose QML code, but at least, it solves the problem:

    Project {
        Testcase {
            name: "test1"
            Exports {
                property string result
            }
    
            run: {
                result = "yes"
            }
        }
    
        Testcase {
            name: "test2"
            Depends { name: "test1" }
            property someProperty: dependencies.test1.result
    
            /* ... */
    }
    

    I might also experiment with sub-classing Testcase from QQmlPropertyMap so that I can skip the intermediate dependencies.


Log in to reply