Moderne Apps mit Qt, QML und C++

2011-05-27 17:23


Moderne, zeitgemäße Apps lassen sich mit Hilfe von Qt und QML recht einfach entwickeln. Meine Motivation für diesen Blogpost ist, aufzuzeigen, wie man die Welten von C++ und QML verbinden kann, wie man die Stärken von QML und C++ am besten nutzt. Auch möchte ich die ersten eigenen Erfahrungen mit QML und C++ in diesem Blogbeitrag festhalten. Mit QML kann man moderne Oberflächen recht einfach gestalten, im Qt SDK ist sogar schon ein visueller Editor enthalten. Erste Erfahrungen mit QML habe ich schon vorher beim Schreiben eines Fachartikels und eines Blogbeitrages zu QML gesammelt.

Spätestens seit dem die Pläne für Qt5 konkreter geworden sind, ist klar, das Apps in Zukunft mit QML gestaltet werden sollten. Dies war auch für mich Motivation, eine eigene App in QML zu erstellen, welche aber auch ein C++ Backend hat. Man kann zwar auch Apps in purem QML + Javascript schreiben, aber ich halte es für besser ein C++ Backend für gewisse Aufgaben zu haben, und denke auch, das die Performance von C++ deutlich besser ist. Auch wenn man für die Performance in QML mit Open Scene Graphen schon einiges erreicht hat. Die Hauptzielplattformen für eine solche App sind momentan wohl Symbian (1 und 3) sowie Maemo und MeeGo. Langfristig wird hier auch Android hinzukommen, da der Qt Port auf Android wohl bald stabil sein wird. Damit kann man mit einer solchen App dann über 50% des Smartphonemarktes abdecken.

Aufbau unserer App

Als erstes möchte ich etwas auf die Architektur der App eingehen, und wie diese Schichten verbunden sind.
Statt Schichten könnte man auch Module oder Layer sagen, eine einfache App verfügt über 2-3 davon:
  • UI
  • Logik
  • Daten/Persistenz (optional)
Für die UI Schicht kommt QML zum Einsatz. QML bildet auch die UI Logik ab, wie zum Beispiel das Wechseln von Ansichten. Dies kann in QML mit States und Transitions komplett implementiert werden. Die Applikationslogik kann entweder in Javascript oder in Qt/C++ implementiert werden, bei komplexeren Apps empfehle ich letzteres. Dann bleibt noch die Persistenzschicht, welche Daten der App speichern soll. Hier kann man häufig SQLite nutzen, auch Textdateien wären für statische Inhalte geeignet.

Die Verbindung dieser Schichten ist relativ einfach, man kann einmal SIGNAL/SLOT zur Kommunikation zwischen C++ und QML nutzen, und auch C++ Methoden aus Javascript oder QML direkt aufrufen. Die in C++ verarbeiteten Daten und Ergebnisse der Logik können als Qt Modell dann wieder an QML übergeben werden. Dies funktioniert auch für Datenbanken (z.B. QSqlQueryModel oder QSqlTabelModel), auch XML kann mittels QXmlModel an QML deligiert werden.

Ein kurzes Beispiel


Als kurzes Beispiel möchte ich einige Snippets aus meinem RSS Client nun zeigen. Ich habe eine Datenbank, welche die abonnierten Feeds hält, und eine weitere Tabelle für deren Entries. Diese Entries werden dann in QML in einer Liste dargestellt.
Eine QML Anwendung im Qt SDK enthält erstmal nur eine main.cpp, daher benötigt man als erstes eine Application Klasse, welche die entsprechende C++ Logik handhabt. Diese ist von QObject abgeleitet, und kann verschiedene Methoden mittels Q_INVOKABLE QML bekannt machen:

class Application : public QObject
{
    Q_OBJECT
    /* Variablen */
public:
    explicit Application(QObject *parent = 0);
    ~Application();
    Q_INVOKABLE void updateFeeds(QString name ="");
    Q_INVOKABLE void insertFeed(QString url, QString name);
private slots:
    void finished(QNetworkReply* reply);
};

Die Methoden updateFeeds und insertFeed sind von QML aus aufrufbar, dafür muss noch ein Application Objekt erzeugt und QML bekannt gemacht werden:

Application appobj;
FeedEntryModel feedentries;
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("appObj", &appobj);
viewer.rootContext()->setContextProperty("feedentrymodel",&feedentries);
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationLockPortrait);
viewer.setMainQmlFile(QLatin1String("qml/QMLRss/main.qml"));
viewer.showExpanded();

Mittels setContextProperty kann man Objekte von Klassen QML bekannt machen, und dort die mit Q_INVOKABLE deklarierten Methoden aufrufen. Auch für Funktionen kann Q_INVOKABLE nutzen. FeedEntryModel ist hier eine Modelklasse, welche sich von QSqlQueryModel ableitet, und die Einträge der Feeds für die App enthält. Diese lassen sich über einen ListView in QML darstellen, die genaue Darstellung jedes Eintrages wird über ein Delegate in QML komplett umsetzt.
Für FeedEntryModel wird noch der Konstruktor und data definiert:

FeedEntryModel::FeedEntryModel(QObject *parent) : QSqlQueryModel(parent)
{
    setQuery("SELECT url,title,description FROM entries ORDER BY date ASC");
    const char* COLUMN_NAMES[] = {
     "url",
     "title",
     "description",
     NULL
    };
    QHash<int,QByteArray> hashes;
    for(size_t i =0; COLUMN_NAMES[i];++i)
        hashes[Qt::UserRole + i + 1] = COLUMN_NAMES[i];
    setRoleNames(hashes);
}
QVariant FeedEntryModel::data(const QModelIndex  & index, int role) const
{
    if(role < Qt::UserRole)
    {
        return QSqlQueryModel::data(index, role);
    }
    else
    {
        int columnIdx = role - Qt::UserRole - 1;
        QModelIndex modelIndex = index(index.row(), columnIdx);
        return QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
    }
}

Die App verwendet C++ um XML Feeds zu parsen, und dann entsprechend in die Datenbank einzutragen. Ebenso stellt diese Schicht Modelle für QML zur Verfügung, welche den Zugriff auf die Daten ermöglichen. In QML ist dann die entsprechende UI für die App implementiert.

Weiterführende Links:
Einfache C++ Modelklassen in QML:
http://cdumez.blogspot.com/2010/11/how-to-use-c-list-model-in-qml.html
Komplexere C++ Modelklassen in QML:
http://cdumez.blogspot.com/2010/12/expose-nested-c-models-to-qml.html
QML Dokumentation:
http://doc.qt.nokia.com/4.7/qtquick.html

Zurück