qgraphicsscene used as a qabstractitemview iii

5 oct 2010

motiviation

this posting is about using a QGraphicsScene/QGraphicsView with a QAbstractItemModel. i’ve blogged about this two times, see [1] and [2]. i’ve made progress and i created a few new concepts which might help you and probably saves a lot of your time.

it is important not to mix the two different Qt concepts: ‘Qt way of ModelViewController’, so let’s revisit the docs:

  1. a QGraphicsScene/QGraphicsView has it’s own model and data storage. one QGraphicsScene (scene) can have several QGraphicsView(s) (views) attached
  2. a QAbstractItemModel is used with QTreeView/QTableView and QListView

IMPORTANT: both concepts (1) and (2) can’t be combined directly - normally (out of the box). what i’m doing here is actually combining them. as you will see, this can be quite complicated.

the basis

the first blog posting refers to the automate project, the second to the ‘spring random map generator’. this posting is mainly about the ‘spring random map generator’. currently the automate codebase is deprecated and broken. i will eventually fix it. this is how the ‘spring random map generator’ looks like:

so what is new since the last blog posting about this?

  1. i’m not inheriting from QAbstractItemView anymore, instead i’m using a QGraphicsScene connected to the QAbstractItemModel using Qt’s ‘signals&slots’
  2. i’ve developed a concept for setting up modules and connections in arbitrary order: that means you can add a connection to the QGraphicsScene without having any module! this means you can remove QGraphicsItem’s in arbitrary oder as well (no more crashes when shutting down the program)
  3. both concepts are implemented in a messanger class: GraphicsItemRelay and a interface: GraphcisItemRelayInterface
  4. module properties can be edited using a QTreeView (accessing the same QAbstractItemModel as the GraphicsScene), using a QSortFilterProxyModel
  5. how to use graphviz to layout a graph

so let’s have a detailed discussion about every of those points.

1. why not inherit from QAbastractItemView

since most of the code given by the QAbstractItemView wasn’t used anyway i removed it (refactoring). now the GraphicsScene is the new view and functionality is added using direct signal&slot connections.

  • connect( model, SIGNAL(modelReset ()), this, SLOT(reset()));
  • connect( model, SIGNAL(layoutChanged ()), this, SLOT(layoutChanged()));
  • connect( model, SIGNAL(rowsInserted ( const QModelIndex & , int , int )), this, SLOT(rowsInserted ( const QModelIndex & , int , int )));
  • connect( model, SIGNAL(rowsAboutToBeRemoved ( const QModelIndex & , int , int )), this, SLOT(rowsAboutToBeRemoved ( const QModelIndex & , int , int )));
  • connect( model, SIGNAL(dataChanged ( const QModelIndex & , const QModelIndex & )), this, SLOT(dataChanged ( const QModelIndex & , const QModelIndex & )));

that is basically all you need to do, if you want to use your QGraphicsScene as a View on top of a QAbstractItemModel. it’s better to have less code to care about!

2. adding/removing Modules/Connections in arbitrary order

my first implementation of adding nodes and connections between them was very limited. see [4], the automate project. as a arrow in the diagramscene [5] i needed both other QGraphicsItems first in order to know the source and the destination coordinates to draw a line.

  • that meant on the other hand: one would have to insert all nodes first and then all connections later when a QGraphicsScene/QGraphicsView got populated with items
  • removing a a QGraphicsScene/QGraphicsView implies the same in opposite direction: first all connections, later all nodes

not doing so would crash the program as the draw routine of the QGraphicsView, when drawing a connection, would query a QGraphicsItem’s position (the node) which was simply not there anymore. note: this has a cyclic dependency:

  • moving a node (QGraphicsItem) does for a connection (QGraphicsItem) to redraw
  • removing a connection requires both nodes (QGraphicsItem) to be there in order to unregister the connection (see previous point)

see http://github.com/qknight/automate/blob/595770383e34f4bea4beedb99aaa13e6b52b910d/src/GraphicsView/SceneItem_Node.cpp:

QVariant SceneItem_Node::itemChange ( GraphicsItemChange change, const QVariant &value ) {

to sum up: to reduce code and complexity it is important to make insertion and deletion in arbitrary order, see (3) how i did it.

3. the GraphicsItemRelay and the GraphicsItemRelayInterface

since problem (2) only requires the position of an object i decided to implement a messenger class:

the concept might look quite complex but it works great as one can remove connections or modules (ports) in arbitrary order, just what i discussed in (2).

4. module properties and a QTreeView

in the last posting [2] i wondered how to implement properties without adding yet another hierarchy layer. it seems it can be done by doing so. see the image in this posting: it features two different views using the same QAbstractItemModel.

again, what was the problem?

  • a list of items should be editable just as done in ‘qtdesigner’ where you have a nice list on the right side hosting QObject attributes
  • every element in the list should be accessed via a QModelIndex so that the model is used to set/get the values
  • every time a property changes the view should be updated as the rendered image might (very likely) change as the parameter influence the image rendering process

the problem was with the hierarchy layer:

  • rootitem - module - port - connection

which was extended by:

  • rootitem - module - port - connection
  • rootitem - module - property

as there are two different views:

  • QTreeView: used to make properties editable
  • QGraphicsScene/QGraphicsView to visualize the graph

that means we have to take care for the third layer as there can be a port or a property.

the QGraphicsScene/QGraphicsView ignores properties. as it simply ignores them. the QTreeView would render ports but to avoid that one can use a QFilterProxyModel which filters out all ports (and all childs of them). this way only modules and properties are there.

5. how to use graphiviz to layout a graph

i always wanted to use graphiviz to layout my automate. i’ve found a implementation at [7] which describes how to do so. i did not find the time yet to use that code. maybe i find the time in the future.

article source