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:

      Q_INVOKABLE
          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;
    wlv.setMetaObject(&MyWidget::staticMetaObject());
    wlv.setModel(model);
    wlv.show();
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 http://github.com/rburchell/qt-interview-widgets - 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.

7 comments:

  1. Isn't it reinventing KWidgetItemDelegate in a more memory consuming way?

    http://lxr.kde.org/source/KDE/kdelibs/kdeui/itemviews/kwidgetitemdelegate.h

    ReplyDelete
  2. Hi,

    Thanks for the reference. Someone else mentioned it to me, but I hadn't yet had the time to take a look.

    I can't rely on KDE code for the work I'm doing at present unfortunately (the size compared to the gain doesn't justify it), but I'll certainly check it out and see if there's anything I can learn from it.

    (Also important to stress that I'm not just done with it, yet. There are other things I want to do.)

    WRT memory usage: I don't yet have good numbers (I want to take the time to get them done properly) to show, but the 1000 row model wasn't using up too much of my n900, so while it is larger than I'd like, it's still usableish.

    ReplyDelete
  3. After reading up on a few usages of KWidgetItemDelegate, I'm not so sure.

    It still seems to involve a *lot* of hand-performed drawing using QPainter instead of designer-based widgets, which was largely the point of the exercise: to not have to do painting in the form of a delegate.

    ReplyDelete
  4. No, it doesn't. And no, you do not need a whole bunch of KDE code. You can use just the KWidgetItemDelegate class with ease, if you want. It is pretty self-contained.

    You should also look into ItemViews-NG. The Trolls are aware of the problems with the current architecture.

    ReplyDelete
  5. Hi André,

    I've seen ItemViews-NG, but I'm not convinced. I QML's approach is quite good, and (I'd guess) is probably going to keep getting pushed into a more usable state.

    Either way, future projects don't help me now, unfortunately :(

    ReplyDelete
  6. Did you look into QItemEditorFactory?
    http://doc.qt.nokia.com/4.6/qitemeditorfactory.html

    This example shows how to set any widget to be the delegate for any QVariant data type. http://doc.qt.nokia.com/4.6/itemviews-coloreditorfactory-window-cpp.html

    I've never had to write a delegate that painted thanks to this, just create a widget (in designer if you want) and set it to be the delegate for your data type.

    ReplyDelete
  7. @Adam: thanks, I'll have a look. Sounds interesting!

    ReplyDelete