Calling a C++ function inside a property subclass from QML



  • Hi,

    I'm trying to do the following:

    in main.cpp: context->setContextProperty("class", new Class);;

    in qml file:

    @
    Button {
    id: btn
    image: "img.svg"
    onButtonClicked: { class.subclass().method(); }
    }
    @

    And it simply doesn't work. Subclass is a property of class, but is itselft a class with it's own methods, properties, slots and signals.
    I've tried everything: qRegisterMetaType, qmlRegisterInterface, Q_DECLARE_METATYPE, but nothing works. Something like this:

    @
    Button {
    id: btn
    image: "img.svg"
    onButtonClicked: { class.method(); }
    }
    @

    Works just fine. But it's now how I want the API to be. Is there any way for this to work, or is this a QML limitation?



  • It should work if you have subclass() and method() as Q_INVOKABLE (or slots) and have invoked qmlRegisterUncreatableType (or another function from this set) for both classes.



  • [quote author="luizpaulo" date="1291472959"]
    @ onButtonClicked: { class.subclass().method(); } @
    ...
    @ onButtonClicked: { class.method(); } @
    [/quote]

    While this should be possible with the suggestion by Denis, consider that in the first variant, the QML UI makes more assumptions about the class hierarchy, while the second variant does not give any information about the class hierarchy away.

    What if you decide in a few months that having subclass() as method of class is not a good idea, and you refactor the code? Then suddenly you have to make changes to your QML - there are no compiler warnings for you when you try to reference something non-existing in the QML, so the bug might not jump right in front of your eyes.

    So the second variant without the subclass() might be more suitable and future-proof and will allow you to refactor the code without changing the QML files. The only thing you have to keep stable in this case is the "public" API of your code that gets accessed directly from QML. It might also create a nicer API and a better structured code.



  • thp, we have similar way as author has to call c++ from qml. In our design we have on class that has pointers to all objects that are needed for qml interaction and doesn't have any methods other than simple getters for this objects. This gives us ability to pass only one object as contextproperty to qml for all interaction between qml and c++.



  • Well it's not working...
    A more complete example follows:

    class1.h:

    @
    class Class1 : public QObject
    {
    Q_OBJECT
    Q_PROPERTY(SubClass1 subclass1 READ subclass1)

    private:
       SubClass1 _subclass1;
    
    public slots:
       SubClass1 subclass1() {return _subclass1;}
    

    };
    @

    subclass1.h:

    @
    class SubClass1 : public QObject
    {
    Q_OBJECT

    public:
        SubClass1(QObject *parent = 0);
        SubClass1(const SubClass1 &);
        SubClass1 &operator=(const SubClass1 &);
    
    public slots:
        void method() {qDebug("method");}
    

    };
    @

    main.cpp:
    @
    ...
    qRegisterMetaType<SubClass1>("SubClass1");
    qmlRegisterInterface<Class1>("Class1");
    qmlRegisterInterface<SubClass1>("SubClass1");

    context->setContextProperty("Class1", new Class1);;
    

    ...
    @

    qml:
    @
    Button {
    id: btn
    image: "img.svg"
    onButtonClicked: { class1.subclass1().method(); }
    }
    @

    Doesn't work. I've tried qmlRegisterUncreatableType and qmlRegisterType as well it didn't work. All I get is this:
    TypeError: Result of expression 'class1.subclass1' [QVariant(SubClass1)] is not a function.

    If I remove the MetaType stuff I get this:
    TypeError: Result of expression 'class1.subclass1' [undefined] is not a function.

    Any other ideas?



  • Is it case sensitive? I never tried this up to now, but in C++ you have to take care of case... How is this in Qml code? Ifit is case sensitive, it should be:

    @
    Button {
    id: btn
    image: "img.svg"
    onButtonClicked: { Class1.subclass1().method(); }
    }
    @



  • I believe this could be a bug and related to http://developer.qt.nokia.com/forums/viewthread/1764/
    filed as http://bugreports.qt.nokia.com/browse/QTBUG-15712 (Voting on it might get it fixed faster)

    Change your return type to a QObject and use qobject_cast<T>() in your c++ code if you need to access it there also.



  • coderbob, yes qobject_cast will help author in using objects from qml, but if all methods will return qobjects or there will be duplicated methods (one return qobject, one normal type) then it can lead to design problems.



  • Denis, I think you misunderstood the solution. There are no duplicate functions. You have to use QObject to be able to access correctly in QML. The side effect of this is that you lose your defined types in the c++ code. To solve this you have to qcast them when accessed there. Only one function is needed but you will have to cast.

    This is not how I want to write my code but it is the best solution I could come up with to work around this bug until it is fixed.



  • I'm still having this problem on my friend's windows machine.

    Windows:-
    It calls the function as the program loads. But when called again, the program crashes

    Linux:-
    It works for some times. Then it crashes.

    Else, it would say it is not a function. I guess QML is loosing some infomation. Maybe garbage collector is destroying the link to the function after some time.



  • if your app is crashing it's more likely that you have an invalid pointer in the c++ code I would say, but hard to tell without seeing any code or something.
    also just start the app in debug mode then you should see the stacktrace when the app crashes and what function it crashes etc.



  • Here's the QML
    @ListView {
    id: listViewRulesStrength
    MouseArea {
    anchors.fill: parent
    onClicked: {
    listViewRulesStrength.model = m.fce().rulesStrength();
    }
    }
    model: m.fce().rulesStrength()
    delegate: Item {
    Row {
    id: row2
    Text {
    text: modelData.rule
    height: paintedHeight
    }
    }
    }@

    The C++ code for m.fce() in class Mixture

    @FuzzyCompostEngine * Mixture::fce()
    {
    return m_engines;
    }@

    The C++ code for rulesStrenght in FuzzyCompostEngine

    @
    QList<QObject *> FuzzyCompostEngine::rulesStrength()
    {
    qDebug() << "Starting rule strength";
    for(int i = 0;i<m_rulesStrength.size();i++){
    RuleStrength *tmp = (RuleStrength *) m_rulesStrength.first();
    m_rulesStrength.removeFirst();
    delete tmp;
    }

    qDebug() << "rule strength";
    fl::Engine *engine;
    for(int i = 0;i < engines.size();i++){
        engine = engines.at(i);
        fl::RuleBlock *ruleBlock = engine->getRuleBlock(0);
        if(ruleBlock != NULL)
        {
            for(int j = 0; j < ruleBlock->numberOfRules() ; j++){
                fl::Rule *rule =  engine->getRuleBlock(0)->getRule(j);
                scalar degree = rule->firingStrength(ruleBlock->getTnorm(),ruleBlock->getSnorm());
                //qDebug() << "Degree" << degree;
                if(std::isnan(degree) == 0 && degree > 0.01){
                    //rules << QString::fromStdString(rule->getUnparsedRule()) + " : " + QString::number(degree,'f',2);
                    m_rulesStrength.append(new RuleStrength(QString::fromStdString(rule->getUnparsedRule()),degree));
                }
            }
        }
    }
    std::sort(m_rulesStrength.begin(),m_rulesStrength.end(),RuleStrength::isGreater);
    qDebug() << "Ending";
    return m_rulesStrength;
    

    }
    @

    When it crashes, it doesn't even start the C++ function rulesStrength. because the qDebug doesn't get displayed

    Sometimes when clicking a lot, i get the error

    bq. qrc:/qml/mainwindow.qml:274: TypeError: Cannot call method 'rulesStrength' of null

    However, the problem gets fixed when i call the function from mixture directly.

    new QML model in listwidget
    @
    MouseArea {
    anchors.fill: parent
    onClicked: {
    listViewRulesStrength.model = m.rulesStrength();
    }
    }
    model: m.rulesStrength()
    @

    I added a function in Mixture like this to call fce() from inside

    @QList<QObject *> Mixture::rulesStrength()
    {
    return fce()->rulesStrength();
    }@

    Now it runs fine on windows and doesn't crash or lose the object in QML on Linux

    Reminder: The initial code does run fine as it should have for some time only on Linux.



  • When you use a construct like
    @m.fce().rulesStrength()@

    you need to be careful about object ownership of the object returned by fce(). If this object has no explicit parent set or doesn't have explicit CppOwnership set, then QML will assume ownership and will destruct it when it is no longer referenced.

    You can check if it gets destructed by setting a breakpoint in the desctructor for example.


Log in to reply
 

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