Software
12th December 2015 | By:

Writing a GUI using QML for a C++ project

As I mentioned before, QML provides a brand new approach to developing GUI applications for a range of different devices. This makes it really easy to write C++ apps for multiple operating systems. We can develop the same QML and QtQuick application for both desktop and mobile.

A while ago one of my colleagues wrote simple program to show how easy it is to download a file using QT’s built-in classes. I decided to use this application for my example, and wrote the front-end in QML!

You can see how the original class looked here.
And the final working GUI for that class in the GitHub repo can be accessed here.

 

The most important requirement is to inherit the class we want to use in QML from QObject. By inheriting from QObject we’re enabling a few special functions provided by QObject.

class Downloader : public QObject{
    Q_OBJECT
public:
    .
    .
signals:
    .
    .
    .
public slots:
    .
    .
    .
};

Now we need to create a new object and pass it to the QML file as the root context. After that we will be able to use it as a Javascript object and use all of its signals and public slots. This is how our main function will look:

int main(int argc, char *argv[]){
    QApplication app(argc, argv);
    Downloader *d = new Downloader();
    QQmlApplicationEngine engine;
    QQmlContext *ctx = engine.rootContext();
    ctx->setContextProperty("downloader", d);
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
    return app.exec();
}

The really important thing to notice is that when we are passing a single pointer to an object that inherits from QObject everything works out of the box because this pointer is automatically cast to a QObject. However when we’re using a list of pointers, we must cast each pointer inside the list to QObjects manually. If we don’t do that, our object and its properties won’t be accessible from the QML document.

The next step is to specify which functions and fields we want to have available in QML. By default only signals and public slots are available (as they are implemented inside QObject), any other parts of our class are ordinary C++ methods and fields and are therefore invisible in QML.

To use one of the fields from our class we need to create setter and getter functions. It is also good practice to create a NOTIFY signal that will be fired each time the value of our property changes. The main pattern looks like this:

Q_PROPERTY(type name READ getName WRITE setName NOTIFY nameChanged)

Where:
– ‘type’ specifies the returned type of the property like int, QString or our custom class type
– ‘name’ is the name of our property, this name will be available in the QML document
– ‘getName’ specifies the getter method
– ‘setName’ is the method that we are passing arguments to, to set a new value of the property, this method should emit the ‘nameChanged’ signal when the value is successfully changed
– ‘nameChanged’ signal emitted (manually, by programmer) each time the value has changed

We can expose class methods as well. To do this, Qt provides another macro ‘Q_INVOKABLE’. We are enabling access to the function by putting this macro before the method definition.

Lets look at an example where we will use Q_PROPERTY and Q_INVOKABLE:

class Animal : public QObject{
    Q_OBJECT
public:
    Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
    Q_INVOKABLE bool hasName(){
        return m_name.isEmpty();
    }
    QString getName(){
        return m_name;
    }
    void setAge(QString name){
        // some validation if needed
        m_name = name;
        // notify listeners that our value has changed
        emit nameChanged();
    }
signals:
    void nameChanged();
private:
    QString m_name;
};

When using in QML, assuming that we are passing `animal` as the context variable:

function initAnimal(){
    console.log(animal.isEmpty()) // prints true
    console.log(animal.name) // prints ""
    animal.name = "Ben" // Name will be changed and signal nameChanged will be emitted.
    console.log(animal.isEmpty()) // prints false
}

In our simple downloader we are only using the Q_INVOKABLE macro. The invokable function is used as the url provider.

class Downloader : public QObject{
    Q_OBJECT
public:
    Q_INVOKABLE void downloadUrl(const QString &);
    .
    .
    .
    signals:
        void progress(qint64, qint64);
        void finished();
        void downloadError(QNetworkReply::NetworkError);
    .
    .
    .
};

Now in QML we can use the Downloader object as the Javascript object with all benefits coming from C++ and QT as signals slots and invokable methods.

downloader.downloadUrl("some url");

There is also an easy way to connect signals that belong to the QObject with Javascript functions. It allows us to show on the front end each change that fires signal. Each QML element has special functions that are fired when a particular state is reached. When the element is completely loaded the `Component.onCompleted` function is fired. This is a good place to specify our connections:

Component.onCompleted: {
    downloader.progress.connect(downloadProgress);
    downloader.finished.connect(downloadFinished);
    downloader.downloadError.connect(downloadError);
}

If our signal has any arguments, they will be available in the Javascript function as well.

function downloadProgress(val_a, val_b){
    ...
}
function downloadFinished(){
    ...
}
function downloadError(err){
    ...
}

As you can (hopefully!) see it is really straight forward and easy to create a GUI for C++ applications using QML. It’s far more flexible than the QtWidget applications, and we can write the backend totally separate from the front end.

Tags: , , , , ,