Itemviews – The Next Generation

Published Friday October 24th, 2008
26 Comments on Itemviews – The Next Generation
Posted in Graphics View, Itemviews, Labs

I figured it was time I blogged about my Creative Friday project, so here goes!

“Lieutenant. Do you intend to blast a hole in the viewer?”
– Picard to Worf in “Encounter At Farpoint”

As you may be aware of, the Qt Item Views is a set of widgets and classes that allow you to display, navigate and edit data, separating the data from the presentation (model/view).
I’ve had many discussions with customers and colleagues (and random people who just seemed to have opinions on the subject) about the item views over the years. You could say it is a hot subject.

Not surprising. If you are using Qt, chances are you are using the item views. And if you are using the item views, you are likely to have an opinion about them.
While using these classes you may have even form the opinion that they are not exactly the most shining example of Qt quality framework and API design. Let’s just say that there is room for improvement, lots of room!

Troi: “What happened to all the people?”
Worf: “War?”
Data: “Disease?”
Geordi: “A dissatisfied customer?”
– Troi, Worf, Data and Geordi “The Arsenal Of Freedom”

The symptoms are clear: the framework is generally too slow, unstable and the API is difficult to use. A clear case of overly complex design. But where does all this complexity stem from ?

“It’s elementary, my dear Riker. Sir.”
– Data in “Lonely Among Us”

The cause is the feature that has been both a blessing and a curse for item views.
The framework has been designed to trees, tables and lists interchangeably. You can use a list view to show a tree, a tree view to show a table and so on. A pretty cool and sometimes very useful feature.
But it also means that all parts of the framework has to work with a structure that can represent a tree, a table or a list. So, otherwise simple cases become complex because of the inherent complexity of this structure.

“Data, you all right ?”
“Yes sir, I’m fine.” [Contraction!]
“Then get rid of that damn twitch and put on the correct uniform.”
“Yes, Captain.”
– Picard and Data in “Datalore”

The cure is pretty obvious: separate the list, table and tree from each other.
This is the basis for my itemviews-ng research project. It is a complete re-design of all the components in the item views framework, with one overriding principle in mind:
Keep It Simple, Stupid!

“This is called a banana split. It’s quite possibly one of the greatest things in the universe.”
– Wesley in “Suddenly Human”

But there are more reasons to have another go at implementing item views.
Item views can’t get away with just displaying items anymore. They need to be animated, allow flick-scrolling and generally be loaded with snazzy graphical effects. Items fly in from all sides, zoom, rotate, blend or do other fancy stuff. The current item views are simply outdated.

“Can you recommend a way to counter the effect?”
“Simple. Change the gravitational constant of the universe.”
– Data and Q in “Deja Q”

The good news is that we already have something in Qt that allows us to do all this: the QGraphicsView. So it just makes sense that we use QGraphicsView and QGraphicsItem with the new item views.

As an experiment I’ve decided to implement the internals of the views as QGraphicsWidgets (called a control for now, although that name is slightly confusing). This allows you to use item views as elements in a graphics scene.
There is also a QWidget-based view (it actually inherits QGraphicsView) that wraps the control and also provides a default data model and selection manager.

path.png

For a QListViewNG you can use QListControl, QGridListControl or QPathListControl. In addition you always have the option of implementing your own custom control to get the layout and/or painting you want.

“Maybe Q did the right thing for the wrong reason.”
“How so ?”
“Well, Perhaps what we most needed was a kick in our complacency to prepare us for what lies ahead.”
– Picard and Guinan in “Q Who”

But enought talk, more rock! The code is available here:

Check it out and enjoy!

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in Graphics View, Itemviews, Labs

26 comments

Benjamin Meyer says:

Using the 4.5 snapshot I get build/linking errors on the examples.

JarJarThomas says:

juhuuu a redesign of the model/view framework … it’s the only thing i hate about qt. And i REALLY mean hate.

Johan says:

“juhuuu a redesign of the model/view framework … it’s the only thing i hate about qt ”

Thank god….I’m no alone ;)!

Seriously….I agree that the current architecture is cumbersome and has a learning curve so maybe redesign needs to be done. But please, people at Trolltech, take your time and do it right this time!

Otherwise, Qt is propably the best I’ve seen when it comes to libraties. Carry on the good work!

Andrea says:

Also with Qt4.4.

Avi says:

Great post. It’s nice to see that someone is finally checking the over-complicated model/view classes.

That is almost exactly the behavior that I’m trying to implement in my project. It would be nice if qt allow to do this, because I already have tons of code implementing that.

Giovanni Bajo says:

Eheh I thought I was the only one advising *against* the item/views classes 🙂

Nils says:

The current model view stuff really isn’t that bad. Issues are:

– Integration with the SQL Module is buggy, I’d never use it again
– The documentation is too complex. There exists a paper by Trolltech that does a much better job explaining the framework.
– Drag&Drop implementation within the views sucks. You just don’t have enough control about where the user is able to drop things. The “drop indicator” is ugly.
– Animation would be a welcome addition.

IMHO all these issues could be solved within the current framework, all it needs is some love and care.
The post sounds as if the current ItemView classes are already considered as being deprecated.

Nils

David Johnson says:

I don’t advise against the item/view classes, but at the same time they are not the universal MVC API some people make them out to be.

Mike says:

Why are you still imposing a data model of any kind at all? Aren’t there parts of the API that could be data model independent? Honestly, I’ve never seen an MVC which was so mostly useless. It’s much like many MS APIs, which when used in the precise way that was envisioned by the designers, works great, but deviate even a little bit from than and all hell breaks loose.

Wysota says:

Itemviews-ng finally announced! Hurray! 🙂

mbm says:

Ben: It should work fine with the Qt 4.5 tech preview. If you have problems with the tech preview package, let me know (compiler output might be useful).
Andrea: itemviews-ng requires Qt 4.5. I’ve added a protected constructor to QGraphicsView in Qt 4.5 that allows me to inherit its private class.
Mike: I think MVC has its uses, but as always you have to use the right tools for the job. I’d be interested in what you think would be a better approach. 🙂

Benjamin Meyer says:

QGraphicsItemPrivate is not exported. It only has a Q_AUTOTEST_EXPORT so you only get the symbol if you are using the Trolltech version of the Qt, not the public versions.

ben@benjamin-laptop:~/dev/itemviews-ng/examples/tree$ make
g++ -Wl,-rpath,/home/ben/dev/qt-snapshot/lib -o tree .obj/main.o .obj/test.o .obj/test2.o -L/home/ben/dev/qt-snapshot/lib -L ../../lib -litemviews-ng -lQtGui -L/home/ben/dev/qt-snapshot/lib -L/usr/X11R6/lib -pthread -lpng -lgobject-2.0 -lSM -lICE -pthread -pthread -lXi -lXrender -lXrandr -lfreetype -lfontconfig -lXext -lX11 -lQtCore -lz -lm -pthread -lgthread-2.0 -lrt -lglib-2.0 -ldl -lpthread
../../lib/libitemviews-ng.so: undefined reference to `typeinfo for QGraphicsItemPrivate’
../../lib/libitemviews-ng.so: undefined reference to `QAbstractScrollAreaPrivate::contentsOffset() const’
../../lib/libitemviews-ng.so: undefined reference to `vtable for QGraphicsItemPrivate’
../../lib/libitemviews-ng.so: undefined reference to `QGraphicsItemPrivate::isProxyWidget() const’
../../lib/libitemviews-ng.so: undefined reference to `typeinfo for QAbstractScrollAreaPrivate’
../../lib/libitemviews-ng.so: undefined reference to `QAbstractScrollAreaPrivate::scrollBarPolicyChanged(Qt::Orientation, Qt::ScrollBarPolicy)’
../../lib/libitemviews-ng.so: undefined reference to `QGraphicsItemPrivate::inputMethodQueryHelper(Qt::InputMethodQuery) const’
../../lib/libitemviews-ng.so: undefined reference to `vtable for QAbstractScrollAreaPrivate’
collect2: ld returned 1 exit status
make: *** [tree] Error 1

Matthias Ettrich says:

@mike: He is not imposing data models, he is defining standard APIs to access the data. I am curious how you would want to do it differently, and still offer something useful. Generic MV that does not impose anything would end up looking like this:
class View : public QWidget { Q_OBJECT public: View(QWidget *parent=0):QWidget(parent){} public slots: void updateView() = 0; };
class Model : public QObject { Q_OBJECT public: Model(QObject *parent=0):QObject(parent){} signals: void modelChanged(); };

You connect Model::modelChanged() to View::updateView() and off you go! But this won’t help developers at all who want to add lists and trees to their applications.

I’m obviously not getting something here. What is it you guys want when you envison generic MV(C) frameworks? Please elaborate. Isn’t splitting painting, event handling, and storing data in three classes just for the sake of splitting far more cumbersone than writing a componen, which also can be modified in subclasses by reimplementing virtual functions? And if it’s truly generic, how can a framework help there? Will not any assumption you do in the framework be perceived as “imposing data models”?

If the concept is useful, I’m positive there must be some good generic MVC frameworks we can have a look at for inspiration. Links highly appreciated.

Sylvain says:

it does not compile on macosx with Qt4.5 TP

Sylvain says:

neither on the latest snapshot:

g++ -c -pipe -g -gdwarf-2 -Wall -W -fPIC -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/local/Trolltech/Qt-4.5.0/mkspecs/macx-g++ -I. -I/usr/local/Trolltech/Qt-4.5.0/lib/QtCore.framework/Versions/4/Headers -I/usr/local/Trolltech/Qt-4.5.0/include/QtCore -I/usr/local/Trolltech/Qt-4.5.0/lib/QtGui.framework/Versions/4/Headers -I/usr/local/Trolltech/Qt-4.5.0/include/QtGui -I/usr/local/Trolltech/Qt-4.5.0/include -Isrc -I.moc -I. -F/usr/local/Trolltech/Qt-4.5.0/lib -o .obj/qlistiteminterface.o src/qlistiteminterface.cpp
src/qlistiteminterface.cpp:26:31: error: private/qobject_p.h: No such file or directory
src/qlistiteminterface.cpp:28: error: invalid use of undefined type ‘struct QObjectPrivate’
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:62: error: forward declaration of ‘struct QObjectPrivate’
src/qlistiteminterface.cpp: In member function ‘QListItemInterface* QListItemInterfacePrivate::q_func()’:
src/qlistiteminterface.cpp:30: error: ‘q_ptr’ was not declared in this scope
src/qlistiteminterface.cpp: In member function ‘const QListItemInterface* QListItemInterfacePrivate::q_func() const’:
src/qlistiteminterface.cpp:30: error: ‘q_ptr’ was not declared in this scope
src/qlistiteminterface.cpp: At global scope:
src/qlistiteminterface.cpp:72: warning: unused parameter ‘from’
src/qlistiteminterface.cpp:72: warning: unused parameter ‘to’
src/qlistiteminterface.cpp:72: warning: unused parameter ‘count’
src/qlistiteminterface.cpp: In constructor ‘QListItemInterface::QListItemInterface(QListModelInterface*)’:
src/qlistiteminterface.cpp:104: error: no matching function for call to ‘QObject::QObject(QListItemInterfacePrivate&, QListModelInterface*&)’
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:297: note: candidates are: QObject::QObject(const QObject&)
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:281: note: QObject::QObject(QObjectPrivate&, QObject*)
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:116: note: QObject::QObject(QObject*)
src/qlistiteminterface.cpp: In constructor ‘QListItemInterface::QListItemInterface(int, QListModelInterface*)’:
src/qlistiteminterface.cpp:111: error: no matching function for call to ‘QObject::QObject(QListItemInterfacePrivate&, QListModelInterface*&)’
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:297: note: candidates are: QObject::QObject(const QObject&)
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:281: note: QObject::QObject(QObjectPrivate&, QObject*)
/usr/local/Trolltech/Qt-4.5.0/include/QtCore/qobject.h:116: note: QObject::QObject(QObject*)
make: *** [.obj/qlistiteminterface.o] Error 1

WidgetPimp says:

I tell ya man, it looks like the Itemviews will have some nice bling. But I wonder, man, what took longer? Writing the blog post or findin’ the “Trek Nerd” quotes?

Craig Ringer says:

I’ve been using the tree view & abstract model heavily in a project I work on (PoDoFoBrowser – http://podofo.sf.net/) and did find it tricky. It was difficult to ensure that all events were generated as the view expected, and I had persistent crash/corruption issues when making alterations to the data that the AbstractItemModel was a mapping for.

The main issues I encountered were difficulties in ensuring that the data model always behaved as the viewer expected it to. In particular, I found it very difficult to make changes to a subtree without resetting the view entirely; I had to either refresh the entire tree, or inform the view of each individual change as I made it. Since, in this case, changes were made to subtrees by external code without the application even knowing exactly what had changed, this was … cumbersome. The AbstractItemModel in this case maps an existing concrete data stucture (a cyclic directed graph of nodes) into a tree comprehensible by the Qt item model; it’s a thin and lightweight node tree that just points to the real data.

To solve the refresh problem I ended up writing a dirty hack that caused the item model to lie to the view, claiming that a given node had no children. It’d then be refreshed, and the item model would be told to show the view the real contents again. This was still far from ideal, as it closed the whole subtree, forgetting the state of all nodes under it that were tabbed open etc.

The ideal would be to be able to inform the view that children of node (x) are about to change / have changed, so it should update that subtree while preserving viewer state such as tabbed-open and selected items. An application can probably do this its self by scanning the before- and after- states, generating an edit list, and applying that to the model visible to Qt, but it’d be much nicer for the view to be able to do this its self. I’m not sure the view can have enough information to do that, though, without basically storing a clone of the model. Still, at least a resetSubTree(node*) method on the view would make a huge difference.

Philippe says:

Though the API is complex, I begin to get used to it. My primary complain is about the bad performances.
A couple of years ago, I used the following product, that could do lists, tables and tress, with the same API:
http://www.softelvdm.com/docs/SftTree%20OCX%206.0/
Though the API was no so “object oriented”, both the speed and large feature set were very satisfying.

Hai says:

I can not check out from svn (Err* Can’t connect to host …). Could you put on distribution site or some place that I could view the code?
I am very excited about this project !!!. Will it be in version 4.5?

Thanks.

comment says:

Nice to see that you realized the complexity API of you MV framework.

It is one of the most unintuitive parts of Qt. I always try to only use the available
classes and not to implement any model.

I hope you find a way to improve this.

comment says:

A first step would be: More typing!
tree views for trees,
table views for tables

And don’t start scripting in C++ with help of QVariant! When I wanna use a dynamic_cast as first solution, I prefer using Java.

Having dropped msvc6 you could use more templates (QTreeModel and concepts for T to make it viewable with QTreeView)

mbm says:

Ben: The symbols are indeed missing in the Tech Preview package! The fix is simple, and you can do it in the TP package yourself:
-class Q_AUTOTEST_EXPORT QGraphicsWidgetPrivate : public QGraphicsItemPrivate
+class Q_GUI_EXPORT QGraphicsWidgetPrivate : public QGraphicsItemPrivate
and
-class Q_AUTOTEST_EXPORT QAbstractScrollAreaPrivate: public QFramePrivate
+class Q_GUI_EXPORT QAbstractScrollAreaPrivate: public QFramePrivate
This has already been fixed in the Qt 4.5 depot (which is why I never saw this error) and should be in the final Qt 4.5 snapshots.
Sylvain: should be fixed in the itemview-ng depot. I simply removed QListItemInterface from the project file for now, since it is not used anywhere yet.

Craig says:

This is heading in the right direction but I think the reference object should be completely abstract. You’ve almost done this with the tree model, by replacing QModelIndex with a user specified data type, but there’s still some carryover in the form of an int used to reference columns. In the table model, there is a row and a column so pretty much the QModelIndex approach is still there, it’s just been collapsed into the argument list.

If you make the reference object completely abstract it will allow the users of the model interface to choose the optimal structure for the reference objects, and I think you can also get away from having to split into three interfaces (for list, table and tree), since a completely abstract reference would be equally applicable to any view. A list may indeed be implemented as an array, in which case an int is a perfectly suitable row reference, but it might not. If it was implemented as a binary tree, an int would not make such a good reference. You need to defer that choice to the person who knows what the underlying data model architecture really is.

I’m not suggesting that numeric indices be abandoned, just not made part of the reference object (implicitly or explicitly). The model interface should have methods for returning the nth column, child or row to improve efficiency in the case where the tree knows that the nth column, child or row was selected or clicked. Without this it would have to start at the first element and call next(child|sibling) n times, and chances are the model implementation can do better than that. This offers the flexibility for the view to scan the data sequentially using the the next(…) methods and avoid the potentially high overhead of using direct indexing, while still making it available when necessary (view calls index->reference then uses the reference).

Benjamin Meyer says:

using the snapshot from Tue Nov 4, it still does not compile.

g++ -Wl,-O1 -Wl,-rpath,/home/ben/dev/qt-snapshot/lib -o tree .obj/main.o .obj/test.o .obj/test2.o -L/home/ben/dev/qt-snapshot/lib -L ../../lib -litemviews-ng -lQtGui -L/home/ben/dev/qt-snapshot/lib -L/usr/X11R6/lib -pthread -lpng -lgobject-2.0 -lSM -lICE -pthread -pthread -lXi -lXrender -lfreetype -lfontconfig -lXext -lX11 -lQtCore -lz -lm -pthread -lgthread-2.0 -lrt -lglib-2.0 -ldl -lpthread
../../lib/libitemviews-ng.so: undefined reference to `typeinfo for QGraphicsItemPrivate’
../../lib/libitemviews-ng.so: undefined reference to `vtable for QGraphicsItemPrivate’
../../lib/libitemviews-ng.so: undefined reference to `QGraphicsItemPrivate::isProxyWidget() const’
../../lib/libitemviews-ng.so: undefined reference to `QGraphicsItemPrivate::inputMethodQueryHelper(Qt::InputMethodQuery) const’
collect2: ld returned 1 exit status
make: *** [tree] Error 1

the qlistcontrol also fails to compile, but not with a linker error
tst_qlistcontrol.cpp:120: error: u2018class QListControlu2019 has no member named u2018setRectu2019

Benjamin Meyer says:

Compiling against the Nov 7th snapshot I get new errors

g++ -headerpad_max_install_names -single_module -dynamiclib -compatibility_version 1.0 -current_version 1.0.0 -install_name libitemviews-ng.1.dylib -o libitemviews-ng.1.0.0.dylib .obj/qheadercontrol.o .obj/qlistcontrol.o .obj/qlistmodeladaptor.o .obj/qlistselectionmanager.o .obj/qlistitemmodel.o .obj/qtablecontrol.o .obj/qtablemodeladaptor.o .obj/qtableselectionmanager.o .obj/qtableitemmodel.o .obj/qtreecontrol.o .obj/qtreemodeladaptor.o .obj/qtreeselectionmanager.o .obj/qtreeitemmodel.o .obj/qpathlistcontrol.o .obj/qgridlistcontrol.o .obj/qgraphicsscrollbar.o .obj/qlistviewng.o .obj/qtableviewng.o .obj/qtreeviewng.o .obj/moc_qlistmodelinterface.o .obj/moc_qtablemodelinterface.o .obj/moc_qtreemodelbase.o -L/Users/ben/source/qt-snapshot/lib -lQtGui_debug -L/Users/ben/source/qt-snapshot/lib -framework Carbon -framework AppKit -lQtCore_debug -lz -lm -framework ApplicationServices
Undefined symbols:
“QGraphicsItemPrivate::inputMethodQueryHelper(Qt::InputMethodQuery) const”, referenced from:
vtable for QHeaderControlPrivatein qheadercontrol.o
vtable for QGraphicsWidgetPrivatein qheadercontrol.o
vtable for QGraphicsWidgetPrivatein qlistcontrol.o
vtable for QListControlItemPrivatein qlistcontrol.o
vtable for QTableControlPrivatein qtablecontrol.o
vtable for QGraphicsWidgetPrivatein qtablecontrol.o
vtable for QTableControlItemPrivatein qtablecontrol.o
vtable for QTreeControlPrivatein qtreecontrol.o
vtable for QGraphicsWidgetPrivatein qtreecontrol.o
vtable for QTreeControlItemPrivatein qtreecontrol.o
vtable for QGraphicsScrollBarPrivatein qgraphicsscrollbar.o
vtable for QGraphicsWidgetPrivatein qgraphicsscrollbar.o
“typeinfo for QGraphicsItemPrivate”, referenced from:
typeinfo for QGraphicsWidgetPrivatein qheadercontrol.o
typeinfo for QGraphicsWidgetPrivatein qlistcontrol.o
typeinfo for QListControlItemPrivatein qlistcontrol.o
typeinfo for QGraphicsWidgetPrivatein qtablecontrol.o
typeinfo for QTableControlItemPrivatein qtablecontrol.o
typeinfo for QGraphicsWidgetPrivatein qtreecontrol.o
typeinfo for QTreeControlItemPrivatein qtreecontrol.o
typeinfo for QGraphicsWidgetPrivatein qgraphicsscrollbar.o
“QGraphicsItemPrivate::isProxyWidget() const”, referenced from:
vtable for QHeaderControlPrivatein qheadercontrol.o
vtable for QGraphicsWidgetPrivatein qheadercontrol.o
vtable for QListControlPrivatein qlistcontrol.o
vtable for QGraphicsWidgetPrivatein qlistcontrol.o
vtable for QListControlItemPrivatein qlistcontrol.o
vtable for QTableControlPrivatein qtablecontrol.o
vtable for QGraphicsWidgetPrivatein qtablecontrol.o
vtable for QTableControlItemPrivatein qtablecontrol.o
vtable for QTreeControlPrivatein qtreecontrol.o
vtable for QGraphicsWidgetPrivatein qtreecontrol.o
vtable for QTreeControlItemPrivatein qtreecontrol.o
vtable for QPathListControlPrivatein qpathlistcontrol.o
vtable for QGridListControlPrivatein qgridlistcontrol.o
vtable for QGraphicsScrollBarPrivatein qgraphicsscrollbar.o
vtable for QGraphicsWidgetPrivatein qgraphicsscrollbar.o
“vtable for QGraphicsItemPrivate”, referenced from:
__ZTV20QGraphicsItemPrivate$non_lazy_ptr in qheadercontrol.o
__ZTV20QGraphicsItemPrivate$non_lazy_ptr in qlistcontrol.o
__ZTV20QGraphicsItemPrivate$non_lazy_ptr in qtablecontrol.o
__ZTV20QGraphicsItemPrivate$non_lazy_ptr in qtreecontrol.o
__ZTV20QGraphicsItemPrivate$non_lazy_ptr in qgraphicsscrollbar.o
“QTreeControlPrivate::indentation”, referenced from:
__ZN19QTreeControlPrivate11indentationE$non_lazy_ptr in qtreecontrol.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
make: *** [lib/libitemviews-ng.1.0.0.dylib] Error 1

Commenting closed.

Get started today with Qt Download now