Monday, 14 June 2010

A visual approach to Qt's model/view framework

If you're reading this, you probably use Qt for some reason or other. And if you use Qt, it's only a matter of time before you run into one of the 'pillars' of Qt 4: the Interview Framework, also known as model/view. If you need a little background, read on, otherwise, feel free to skim the next few paragraphs.

Interview is an approach to the problem where you have your data, and you keep it in two places: in your objects, and in your user interface. In more traditional techniques, you have your widgets, and you have your data, and you spend time keeping the two in sync, and it grows kind of organic and messy.

With model/view, you keep everything nicely separated, which is great. That having been said, Qt's implementation isn't really the easiest to use in the world, in some ways. One of my least favourite bits about it is on the view end: delegates. You see, if your UI is anything but plain, everyday, displays of a single string in a list, you'll have to write a delegate.

When writing a delegate, you're pretty much left to your own devices in how you present your data, which is great flexibility - you can paint the 'mona lisa' as the background of all your items or whatever else your heart desires - but at the same time, it is very low level, meaning it is hard for new programmers to wrap their heads around, very easy to make mistakes, both in terms of rendering incorrectly, and really bad performance.

I've mused about this problem in my head for a long time, but more recently, I've had to approach it a few more times, in both FaceBrick and some things I'm working on at Collabora, which proved to be the final straw.

There has to be a better way than telling Qt how to paint everything and where and in what order, like FaceBrick currently does, which also involves a fair chunk of work to attain good performance. It's a daunting task.

QML (so I am informed) has similar principles to what I've applied here, but unfortunately, QML isn't quite ready for the prime time, both in that it requires Qt 4.7, and that it doesn't have a set of established platform widgets - which IMO it needs to prevent every application having to reinvent the wheel.

After some hacking last week, and a little more this week - (with partial thanks again to my employers, Collabora) - I've come up with my initial solution to this problem: QtWidgetListView.

Here's the test I wrote using it, in action on Maemo. The text above the line edits is retrieved from a model, and the line edits + button can be used to update the text, also using the model.

To use QtWidgetListView, here's a rough workflow:
  • Create your own widget subclass
    You can do this using Qt Designer, or write the C++ by hand if you feel like it.

    • Inherit from the QtModelWidget class.
    • Your constructor must be like the following:

          explicit TestWidget(QAbstractItemModel *model, QModelIndex index)
      (In particular, the Q_INVOKABLE and data types are very important)
    • Implement 'virtual void dataChanged()' from QtModelWidget

      This method is called when data in the model relating to your widget changed.

      You should set text on labels, hide/show components, etc here, fetching the data similarly to a delegate:

      m_ui->setText(model()->data(index(), Qt::DisplayRole));
  • Write your model (inheriting QAbstractListModel is the easiest way), making sure you emit the right signals (dataChanged(), etc).
  • Then, do something this:

    MyModel model;
    QtWidgetListView wlv;
And that's it. You should now see widgets created for each of the rows in your model.

Before I forget, I should note that this won't perform as well as delegates - generally speaking. In particular, it will require more RAM, especially if you have a lot of rows.

Now, if you stuck with me for the whole post (or if you skimmed to the bottom because the post was too long and boring..) - the source code for QtWidgetListView is available at - patches and feedback are welcome, and I hope you find it useful. My aim is to flesh things out a bit more and then consider talking to the Qxt folks to get this integrated.