Exposing custom type class instance to QML code



  • Dear all,

    I am about to develop a small labyrinth game with Qt 5.1.1. I would like to implement the whole game logic within C++ and expose the internal state of the "game engine" to QML for visualization. Game Logic and GUI code should talk to each other by using signals and slots.

    The game is based on a labyrinth class. Each level of the game is a subclass of this labyrinth and holds the layout and the items within the labyrinth. Now I would like to set a new level in my application logic and populate the corresponding level data to QML for drawing the whole labyrinth. Therefore, I have a class "Level" which I registered with the Qt meta type sytem using qmlRegisterType() and qRegisterMetaType().
    My game logic emit's a signal

    @signal sigLevelChanged(const Level& newLevel) @

    which should be connected to a function

    @function slotLevelChanged(level)@

    in my QML code. However, I am not able to connect the signal correctly. The compiler always complains that there is no corresponding slot slotLevelChanged(const Level& newLevel) within the QML code available. This is indeed the case as Javascript is an untyped language but how can I deal with this. Do I have to use QVariant in order to send my data to QML code. But then I would somehow need to cast this QVariant back to my "Level" type.

    Furthermore, I don't know how I would be able to access the members of my "Level" class from within QML.
    Do I have to declare all member variables of the "Level" class with the Q_PROPERTY macro in order to be able to access them?

    Last but not least: Is this the preferred way to do what I want to achieve? I know it might be easier to solve this with Javascript but as I am not sure whether to complete the GUI part with QML or QtWidgets I would like to stick to C++ for implementing the game logic.

    Thanks for any comment on this and with best regards,

    Markus

    PS.: I was googeling a lot on this topic and came across the official docs and different websites dealing with this topic. However, I was not able to find a solution for my particular problem.



  • How do you connect signal from C++ class in QML? Is it something like this:
    @
    Level {
    id: level
    onSigLevelChanged: console.log(newLevel)
    }
    @



  • I am trying to use:

    @Application app(argc, argv);
    QtQuick2ApplicationViewer viewer;
    ...
    QQuickItem* pGui = viewer.rootObject();
    QObject::connect(&app, SIGNAL(sigLevelChanged(const Level&)), pGui, SLOT(slotLevelChanged(const Level&)));@

    However, the compiler is not able to map "const Level&" argument to the typeless Javascript argument in "function slotLevelChanged(level)". It was always complains about a missing corresponding slot.

    @QObject::connect: No such slot QQuickRectangle_QML_16::slotLevelChanged(const Level&)@

    Meanwhile, I was thinking whether it would be better to use setContextProperty() for each change of the level and pass a pointer to the level to QML. What do you think?



  • Or set your app object as context property. In C++:
    @
    viewer.rootContext()->setContextProperty("MyApp", &app);
    @
    And in QML:
    @
    Connections{
    target: MyApp
    onSigLevelChanged: console.log(newLevel)
    }
    @



  • Hmmm......well somehow I don't like the idea of setting my whole C++ application object as a context for the QML part. Actually, I would only like to expose specific data structures to the QML code for visualization purpose.

    Anyway, meanwhile I have problems to call overloaded functions from the QML part, although the docs say that this should work. Basically I have two classes:

    @BaseClass
    DerivedClass : BaseClass
    @
    Now I have an instance of DerivedClass. However, I pass this instance as BaseClass* to my QML code and call an overloaded function. Although, the actual object is of type DerivedClass always the base class function i.e. BaseClass::someFunc() is being called. Any idea how this could be fixed?



  • Well You don't have to do it like this, I just showed it as an example. You can create proxy class in C++ and use it to expose to QML only the things you want.



  • Thank you very much for your help and comments on this. Do you have any idea regarding the call of overloaded functions from QML?



  • [quote author="frankem" date="1386165271"]Hmmm......well somehow I Now I have an instance of DerivedClass. However, I pass this instance as BaseClass* to my QML code and call an overloaded function. Although, the actual object is of type DerivedClass always the base class function i.e. BaseClass::someFunc() is being called. Any idea how this could be fixed?[/quote]

    How do you cast the DerivedClass* back to BaseClass*? If you do this with a static_cast, this behavior is expected. Also functions you can call from QML are marked as Q_INVOKABLE or slots. Does your derived class also mark the overridden function as such (should not be a problem when not but who knows).



  • Well, from QML I am calling a C++ function as follows:

    @console.log(_currentLevel.getElement());
    console.log(_currentLevel.getElement().someFunc());@

    The relevant C++ part:

    @BaseClass*** m_Matrix() const;
    BaseClass* Level::getElement()
    {
    return m_Matrix[posx][posy];
    }@

    Before, I set "_currentLevel" context as follows:

    @Level* pLevel = new Level();
    viewer.rootContext()->setContextProperty("_currentLevel", pLevel);@

    The function someFunc() is marked as Q_INVOKABLE in both - BaseClass and DerivedClass as follows:

    @Q_INVOKABLE virtual bool someFunc();@

    However, the first line of the log output in QML always prints "BaseClass*" and so the following call always ends up in BaseClass::someFunc() rather than in DerivedClass::someFunc(). Although, I know that some of the matrix elements are DerivedClass instances.

    Please feel free to ask if you need further clarifications.

    Best regards,
    Markus



  • I've run the test code "here":http://pastebin.com/kH9rZmtd

    With Q_OBJECT in DerivedClass
    @
    DerivedClass(0x11408d0)
    virtual bool DerivedClass::someFunc()
    true
    @

    Without Q_OBJECT in DerivedClass
    @
    BaseClass(0x18025a0)
    virtual bool DerivedClass::someFunc()
    true
    @

    In both cases the correct function is called but if you forget the Q_OBJECT macro, qml only knows about BaseClass and not DerivedClass.

    May this be your issue?



  • Thank you very much for the test code but it didn't seem to do the trick for me. Anyway, I will try to compile the sample code once in a while.

    Currently I have a problem with the type recognition of my context property. I keep on getting the following error during execution:

    @main.qml:35: TypeError: Object #<error> has no method 'rows'@

    Before running the application I am doing this:

    @
    qmlRegisterType<Level>("Level", 1, 0, "Level");
    Level* pLevel = new Level();
    viewer.rootContext()->setContextProperty("_currentLevel", pLevel);@

    Level is a subclass of QObject. Qt Creator recognizes the context property "_currentLevel" as well as it's Q_INVOKABLE-marked functions and properties correctly. However, during execution I am still getting the error from above. Seems like to "Level" type is unknown to the QML part.

    Do you have any idea?


Log in to reply
 

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