Jérémy Dutheil
Jérémy Dutheil

Reputation: 6137

C++ / QML architecture - a way to reproduce C++ structure in QML?

I am currently trying to develop a quite important application (OS-like) with Qt 5.2 and Qt Quick 2 ; what I would like to do is to have all the logic implemented in C++, the UI being declared thanks to QML. At this point, it seems logical and the way to get around. However, I can’t figure how to do it the clean way.. I’ve read a lot of documentation, tutorials and examples but nothing so big…

Let’s explain a little what I would like to put as an architecture ; let’s say we have a Scene object, which could contains an undefined number of Application objects. What I would like is to define the logic in CPP (how I load the applications from XML, what the scene should have as properties, …) and then show the scene with QML. Also, we have to notice that Scene and Application elements should be re-used as component ; so, here is the basic idea : I’d like to define graphical styles that are common to each object with a file in QML (extending the CPP type).

For example, I could create a file with this content :

Application {
    Rectangle { ... }
}

Saying that an application should be representated as a Rectangle ; then, when I create a Scene object that have a list of Application (or one unique Application, to begin with), I would like it to be displayed automatically (‘cause this is a property of Scene object). Is it even possible ? How can I do that ?

I thought that if I extend the C++ object and declare some graphical elements for it, it would be automatic.. But actually it doesn’t look like that !

Maybe there is another way around ?

Thanks

Upvotes: 2

Views: 2577

Answers (2)

Christian Feldbacher
Christian Feldbacher

Reputation: 515

I would advise against using C++ for the logic unless you really need to - use casese to use C++ for the logic are if you have high-performance requirements like realtime data that needs to processed like 10x per second.

As most of the use cases do not have this requirement, it is better to use QML also for application logic because it will save up to 90% source code (and time) compared with C++. Especially in the beginning of development, you are way faster to code the logic in QML and get results faster. You can later on still move the logic to C++ if needed.

There are 2 good guides about this topic available that explain this in more detail and come with source code examples:

  1. QML Architecture Tips and why/how to avoid C++ in your Qt app
  2. QML Architecture Best Practices and Examples

Upvotes: 0

Mitch
Mitch

Reputation: 24396

I don't like this question too much, as it's not really asking anything in particular. The Qt documentation is very comprehensive, so I often find it strange when people say they've read documentation, tutorials and examples, and still haven't found what they're looking for. However, I think I understand the gist of what you're asking, and think the answer could be useful to some, so I'll try to answer it.

main.cpp

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

class ApplicationItem : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QString title MEMBER mTitle NOTIFY titleChanged)
public:
    ApplicationItem(QQuickItem *parent = 0) : QQuickItem(parent) {
    }

public slots:
    void close() {
        emit closed(this);
    }
signals:
    void titleChanged(QString title);
    void closed(ApplicationItem *app);
private:
    QString mTitle;
};

class SceneItem : public QQuickItem
{
    Q_OBJECT
public:
    SceneItem() {
    }

public slots:
    void startApp(const QString &qmlFile) {
        QQmlComponent *component = new QQmlComponent(qmlEngine(this), QUrl(qmlFile));
        if (component->isLoading()) {
            QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)),
                this, SLOT(componentStatusChanged()));
        } else {
            // The component was synchronously loaded, but it may have errors.
            if (component->isError()) {
                qWarning() << "Failed to start application:" << component->errorString();
            } else {
                addApp(component);
            }
        }
    }

    void componentStatusChanged(QQmlComponent::Status status) {
        QQmlComponent *component = qobject_cast<QQmlComponent*>(sender());
        if (status == QQmlComponent::Ready) {
            addApp(component);
        } else if (status == QQmlComponent::Error) {
            qWarning() << "Failed to start application:" << component->errorString();
        }
    }

    void appClosed(ApplicationItem *app) {
        int appIndex = mApplications.indexOf(app);
        if (appIndex != -1) {
            mApplications.removeAt(appIndex);
            app->deleteLater();
        }
    }
private:
    void addApp(QQmlComponent *component) {
        ApplicationItem *appItem = qobject_cast<ApplicationItem*>(component->create());
        appItem->setParentItem(this);

        connect(appItem, SIGNAL(closed(ApplicationItem*)), this, SLOT(appClosed(ApplicationItem*)));

        mApplications.append(appItem);
        delete component;
    }

    QList<ApplicationItem*> mApplications;
};

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

    QtQuick2ApplicationViewer viewer;
    qmlRegisterType<ApplicationItem>("Test", 1, 0, "ApplicationItem");
    qmlRegisterType<SceneItem>("Test", 1, 0, "SceneItem");
    viewer.setMainQmlFile(QStringLiteral("qml/quick/main.qml"));
    viewer.showExpanded();

    return app.exec();
}

#include "main.moc"

I represented both classes as QQuickItem subclasses. SceneItem is composed of many ApplicationItem instances, which are added to the scene by invoking startApp(). This slot takes a path to a QML file as its argument. This file could be loaded over a network, or from a local file, hence we account for the possibility of both synchronous and asynchronous loading.

The QML file should describe the visual appearance of an application, and the scene expects its root type to be ApplicationItem. As an example, here's MySweetApp.qml:

import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0

ApplicationItem {
    id: someAppStyle
    title: "My Sweet App"
    width: 100
    height: 100

    MouseArea {
        anchors.fill: parent
        drag.target: parent
    }

    Rectangle {
        radius: 4
        color: "lightblue"
        anchors.fill: parent

        Text {
            anchors.left: parent.left
            anchors.right: closeButton.right
            anchors.leftMargin: 4
            anchors.top: parent.top
            anchors.topMargin: 4
            text: someAppStyle.title
        }

        Button {
            id: closeButton
            anchors.right: parent.right
            anchors.rightMargin: 4
            anchors.top: parent.top
            anchors.topMargin: 2
            onClicked: close()
            text: "x"
            width: 20
            height: width
        }
    }
}

Applications can close themselves by invoking the close() slot declared in ApplicationItem.

Here's main.qml:

import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0

SceneItem {
    id: scene
    width: 360
    height: 360

    Button {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottom: parent.bottom
        text: "Launch app"

        onClicked: scene.startApp("qml/quick/MySweetApp.qml")
    }
}

This is where the SceneItem is declared, along with a simple interface to launch several instances of My Sweet App (it's a very useful app).

I believe this is the most appropriate way to do what you're asking. It avoids the hassle of setting up lists of ApplicationItems in C++ which are exposed to QML (it's actually not that hard, but this is one area where the documentation could be more obvious), and allows the users of your OS freedom in how applications appear. If you want to be more strict in what can be styled, I'd suggest looking into how Qt Quick Controls does styling.

Upvotes: 1

Related Questions