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.)

Compiling QtDBus for Windows…

I keep needing to do this. Each time I do, I have to rediscover how to do it.

Worse, I’m doing it slightly differently each time.

For interested parties, here’s what I just did for Qt 4.7.1. Worked for Qt 4.7.0, too.

I’m using MinGW, cmake, libexpat, and of course Qt. To begin, you need to build dbus itself for Windows, and then you can rebuild

  1. Make sure you have MinGW installed and correctly configured. Its bin directory should be in your path.
  2. Download cmake. Install.
  3. Download and install libexpat from http://www.winkde.org/pub/kde/ports/win32/repository-4.4/win32libs/(as suggested in the dbus documentation). You’ll want the lib archive. You’ll need the include directory no matter what for when you build QtDBus. Unpack these zips to C:\Program Files\win32libs.
  4. Grab dbus from the repository, put it in c:\dbus-1.4.0.
  5. Edit dbus-1.4.0\cmake\CMakeLists.txt, add
    set(LIBEXPAT_LIBRARIES "C:/Program Files/win32libs/lib/libexpat.lib")
    set(LIBEXPAT_INCLUDE_DIR "C:/Program Files/win32libs/include")
    set(LIBEXPAT_FOUND ON)
    

    after
    project(dbus)

  6. Start a Windows Command Prompt
  7. mdkir c:\dbus && cd c:\dbus
  8. cmake -G "MinGW Makefiles" ..\dbus-1.4.0\cmake -DDBUS_USE_EXPAT=on
  9. mingw32-make
  10. Add c:\dbus\bin to your path.
  11. Edit c:\qt\4.7.1\src\dbus\dbus.pro:
    win32 {
        wince*:LIBS_PRIVATE += -lws2
        else:LIBS_PRIVATE += -lws2_32 \
        -ladvapi32 \
        -lnetapi32 \
        -luser32
        CONFIG(debug, debug|release):LIBS_PRIVATE += -ldbus-1 -Lc:/dbus/bin
        else:LIBS_PRIVATE += -ldbus-1 -Lc:/dbus/bin
        INCLUDEPATH += c:/dbus-1.4.0 c:/dbus
    }
    
  12. You don’t need to build QtDBus separately to begin with the way I do here (step 13) — but it’s much faster to do that and know it’s working than it is to start the configure / mingw32-make step, walk away, and find out an hour later that it died someplace in the middle because you messed up the path to dbus or something.

  13. Start a Qt Command Shell.
  14. cd c:\qt\4.7.1\src\dbus && qmake && mingw32-make
  15. cd ..\.. && configure -dbus && mingw32-make

Your mileage may vary. I seem to remember fixing at least one compilation error in dbus at one point, but don’t remember precisely where or why. It wasn’t hard to do, though — something about a missing argument.

Stay tuned for some Qt tips!

Over the last several months, I’ve been really busy with my coauthor getting my latest book, Beginning Nokia Apps Development finished. Although not a project sponsored by my employer, it’s been exciting working on a book that connects so closely with what I do in the office day-to-day, and my management at Nokia Research Center has been very supportive of the effort, letting me do some of the last-minute edits and tweaks as the schedule got down to the wire. Now that we’re in the last phases of editing (galley proofs, anyone?), I’ve got more time to talk about what I’ve been doing with Qt on mobile devices and other stuff.

As a result, expect a slow but steady stream of posts here in the next several weeks, largely focused on small tips and tricks for working with Qt, especially the Qt Meta-Object Language (QML).