keskiviikko 30. tammikuuta 2013

Searching and manipulating Items in a QML-file through C++/Qt logic

Say you would like to manipulate an Item you have declared in a QML-file through C++/Qt logic. This example is again based on the Qt Quick 2 Application template provided by Qt Creator 2.61, and run on Qt 5.0. We add a new Item (these are called QQuickItems in logic) to the main.qml file with the id "manipulatedItem", so that it looks something like this:

import QtQuick 2.0

Rectangle {
    width: 360
    height: 360
    Text {
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
    Rectangle {
        id: notObjectName
        objectName: "manipulateMe"
        color: "blue"
        anchors.fill: parent
    }
}

Notice that we define the property "objectName" for this new Item. Now we can modify the added QML-item in main.cpp:

#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <QQuickItem>                     // Notice this include

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

    QtQuick2ApplicationViewer viewer;
    viewer.setMainQmlFile(QStringLiteral("qml/untitled1/main.qml"));

    // The findChild-method is used to locate the child object called "manipulateMe".
    // This searching is done recursively, so the whole "branch" is searched. Notice
    // that this searched name is NOT the id of the QML-item, but it is the property
    // called "objectName".
    QQuickItem * manipulatedQQuickItem = viewer.rootObject()->
            findChild<QQuickItem*>("manipulateMe");

    // Now we can manipulate the object through this pointer.
    // But what if the pointer becomes invalid?
    if (manipulatedQQuickItem)
    {
        manipulatedQQuickItem->setOpacity(0.5);
        manipulatedQQuickItem->setProperty("color","red");
    }
    viewer.showExpanded();
    return app.exec();
}

Now you can now modify the Item anyway you like. But there is a risk that the Item gets destroyed before you modify it. So as always you have to be careful with dangling pointers.

sunnuntai 27. tammikuuta 2013

Storing visual QML item (QQuickItems) in C++/Qt data models

Communication between C++ and QML is not always so trivial. Qt provides some very cool ways to pass variables and simple data models to QML by using Q_PROPERTIES and QAbstractItemModel or a more specific models inherited from this. But what if you have to pass more complex data models from C++ logic to QML? How to pass data stored in QMaps and QHashes for example? Atleast as far as I know (please enlighten me if I'm wrong about this) QAbstractItemModel-class can only provide data stored in lists/multidimensional lists/tree-like data models to QML.

The idea behind models is to create visual QML-delegates based on the data stored in the model. And when you change the data stored in the model, the QML-delegates also change accordingly. The problem is that there are no ready models for the more complex datatypes like hashes and logarithmic trees. And in my experience it's very nasty/impossible to try and subclass the QAbstractItemModel for arbitrary data models.

One way to go around this problem is to dump the models between your logic and QML. The trick is to create the visual QML-objects (objects derived from QQuickItem) in your C++ code and store pointers to them into the wanted arbitrary data structure. Then you also have to reparent these QQuickItems to the shown window. The danger here is that you now have a pointer to the QQuickItem in your data structure, but you have granted the ownership of the QQuickItem to the parent item. This can result in dangling pointers, so you have to be careful. To do this you have to break the barrier between logic and QML a bit more than is "recommended". See this page for more discussion on manipulating the QML object tree through C++ (search the word WARNING to find the right paragraph :P ).

Notice that there are two kind of parents for QQuickItems: visual parents and QObject-parents. When you reparent a QQuickItem to another QQuickItem item using the funtion QObject::setParent(), the ownership of the item is transferred to the parent. This ensures that the reparented item is destroyed when the parent is destroyed. On the other hand if you reparent an item using QQuickItem::setItemParent(), you reparent the item only visually. Data ownership is not transferred in this case.

Now follows an example that shows how to create QQuickItems from an QML-file, and visualise them in your view. This example is based on the Qt Quick 2 Application template provided by Qt Creator 2.61, and run on Qt 5.0. The following code is from the main.cpp file and I will explain it a bit more below.

#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <QQmlComponent>                // Notice these includes
#include <QQuickItem>                   // Notice these includes
#include <QQmlEngine>                   // Notice these includes

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

    QtQuick2ApplicationViewer viewer;
    viewer.setMainQmlFile(QStringLiteral("qml/untitled1/main.qml"));
    viewer.showExpanded();
    
    // This part is where we create the QQuickItem based on a QML-file
    QQmlComponent component(viewer.engine(),
                            QUrl::fromLocalFile("qml/untitled1/RedRect.qml"));
    QQuickItem *redRect = qobject_cast<QQuickItem *>(component.create());
    
    // Store the redRect anywhere you like for later access
    // You can later on modify the visual QQuickItem through this pointer
    // Here are some example functions for modification:
    //  -QObject::setProperty()
    //  -QQuickItem::setX()
    std::vector<QQuickItem *> dataStructure;
    dataStructure.push_back(redRect);
    
    // Here we reparent the created QQuickItem (first the traditional QObject
    // parenting and then the visual parenting).
    // WARNING: after this the ownership of redRect is transferred to the
    // rootObject (main.qml), but you also have a pointer to redRect in your
    // data structure. This is very dangerous because the pointer in the data
    // structure can become invalidated. You will have take this possibility
    // into account (use smart pointers or other methods to avoid this).
    redRect->setParent(viewer.rootObject());
    redRect->setParentItem(qobject_cast<QQuickItem *>(viewer.rootObject()));

    return app.exec();
}

As said this example is based on an template in Qt Creator 2.6.1, and you can create the template like this: File->New file or project->Projects->Applications->Qt Quick 2 Application (Built-in elements). In this example I've created a new QML-file called "RedRect.qml" to the project and it simply is a red rectangle (duh).

The "viewer"-variable is basically the main window that shows everything. In this case it is inherited from QQuickView, and thus it is assigned a root object that is defined in the "main.qml". To create a QQuickItem from a QML-file we first create a QQmlComponent. This class is the middle man in creating QQuickItems dynamically. This Component-class has a method called create(), that returns the RedRect as a QObject* so we cast it into an QQuickItem* and call it redRect. Then we store this pointer to the wanted data structure for further access. You can modify the created item through this pointer. The final step is to reparent the created QQuickItem to the view for visualisation and data ownership.

Feel free to comment, and tell me when I'm wrong about something in this entry. I will then update the text accordingly.

perjantai 25. tammikuuta 2013

First post

Testing out the syntax highlighter. Instructions for how to enable syntax highlighting in blogger are found at: http://www.cyberack.com/2007/07/adding-syntax-highlighter-to-blogger.html. Notice that syntax highlighting doesn't work (atleast for me) in dynamic views...

You have to escape the special HTML-characters in the source code. You can use this site to do it automagically.

void SayHello()
{
 std::cout << "Hello World!" << std::endl;
}