Adding C++ Objects to your QML

There’s many times that you may want to access a C++ object from your QML. One obvious example is if you’ve already written a data model in C++; you can leverage that model with QML-based user interface. Any other time you need to access things you can only touch from C++, like hardware integration not provided by Qt Mobility, is another example.

As a simple example, consider a case where you want to expose a C++-based application controller (think model-view-controller) to your QML. You’ve got a class, AppController, with some Qt metaobject invocable methods, either expressed as properties, slots or using Q_INVOKABLE. Mine looks like this:

class AppController : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int width READ width NOTIFY sizeChanged)
    Q_PROPERTY(int height READ height NOTIFY sizeChanged)

public:
    explicit AppController(QObject *parent = 0);
    void setSize(const QSize& size) { mSize = size; emit sizeChanged(); };
    int width() { return mSize.width(); };
    int height() { return mSize.height(); };

   Q_INVOKABLE void magicInvocation();

signals:
    void sizeChanged();

private:
    QSize mSize;
};

Because QML binds using Qt’s metaobject system, the C++ object you want to expose to QML must be a descendant of QObject. Our AppController class is pretty simple; it’s just carrying the size of the window displaying the QML view, along with some C++ method my QML invokes named magicInvocation. (If I told you what it did… you get the idea.)

Of course, we need to fire up a QML viewer with our QML, and add an instance of AppController to the object hierarchy in the QML engine. I do that in my application’s main, which looks like this:

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

    QRect screen = QApplication::desktop()->screenGeometry();
    QMainWindow* window = new QMainWindow(0, Qt::FramelessWindowHint );
    QDeclarativeView* view = new QDeclarativeView(window);
    AppController* controller(window);

    // The only thing we show is the declarative view.
    window->setCentralWidget(&view);

    // Size the window to be as big as it can be, except we don't
    // want it too big on our dev workstations.
    if (screen.width() <= kDesiredWidth) {
        window->showMaximized();
    } else {
        window->setGeometry(QRect(
            (screen.width()-kDesiredWidth)/2, (screen.height()-kDesiredHeight)/2, 
            kDesiredWidth, kDesiredHeight));
        window->show();
    }
    controller.setSize(window.size());

    // Proxy in our app controller so QML get its properties and show our QML
    view->rootContext()->setContextProperty("controller", controller);
    view->setSource(QUrl("qml/main.qml"));

    int result =  app.exec();
    delete window;
    return result;
}

Pretty basic stuff here:

  1. I get the screen size, used by the AppController for its own nefarious purposes.
  2. I create a full-screen main window with no window chrome.
  3. I create a QDeclarativeView to display the QML, and make the main window’s main widget the new QDeclarativeView.
  4. I create an instance of AppController.
  5. I do some funny stuff with the main window’s size so I don’t go crazy working on my desktop’s 22″ monitor, restricting the maximum possible size of the main window for test purposes.
  6. Using the QDeclarativeView‘s QDeclarativeEngine, I add the AppController instance to the QML context, giving it the name controller.
  7. I set the initial source of the QML to the QML entry point for my user interface, included as a file in my application’s package (not as a resource, but you could also choose to package it as a Qt application resource if you want.)
  8. Finally, I pass control to QApplication‘s main loop, and return its result code when its event loop exits.

The magic is QDeclarativeEngine::setContextProperty, which binds a QObject-derived instance to a specific name in the QML context. Once I do this, in my QML I can access this just as I would any QML or JavaScript object; its name is controller. So I might write controller.magicInvocation() to invoke my magic function in an onPressed signal handler, for instance.

(This is well-documented, but I found it handy to break this point out into a separate example to refer to. It’s also a predecessor for several upcoming posts, so it’s here so that those posts can refer back to this one.)

Leave a Reply