PDA

View Full Version : Synchronizing behaviour between QListView and QGraphicsScene



russdot
21st April 2012, 17:28
Hello,
I am aiming to replicate a Photoshop-like selection behaviour between a QGraphicsScene/QGraphicsView and a QTreeView/QStandardItemModel.

Specifically, a QTreeView shows the names of QGraphicsItems and their children contained within a QGraphicsScene (children will be linked with a parent so they move together). When an item is selected in the QTreeView, the corresponding QGraphicsItem is selected in the scene and vice versa.

I was considering making a class which inherits from both QStandardItem and QGraphicsItem (or some subclass... probably QGraphicsPixmapItem) so that a single pointer could be inserted into either the scene or the tree view without having to keep track of all kinds of cross-references. This way I should be able to set up signals so that a change in selection in the tree view or graphics view can signal a selection change in the other.

However, I am still learning about all the Qt classes and their functionality so I don't want to make a big design mistake out of ignorance which ends up haunting me later on.

Any suggestions on ways or best-practices on how something like what I've described might be accomplished? Any help would be greatly appreciated! Thanks.

d_stranz
22nd April 2012, 00:07
If there is always a 1 to 1 relationship between the tree item and graphics scene item, consider using boost::bimap (http://www.boost.org/doc/libs/1_49_0/libs/bimap/doc/html/index.html) where the two "sides" of the map are a pointer to the QStandardItem and a pointer to a QGraphicsItem, respectively. Looking up using one as a key gives you the other, and vice versa.

Mixing up QStandardItem and QGraphicsItem by multiple inheritance seems like a good way to lock yourself into an inflexible design, and it would be a weird class - sometimes it behaves like a car, sometimes it behaves like a banana, so to speak.

It might be feasible to inherit from bimap to create a QObject that could act as the intermediary between the item model and the scene; catch signals from the model or scene, then map those into new signals emitted by the bimap and received by the other partner. This way, neither the scene nor the tree view know anything about each other - all communication is through the bimap.

Note that you can use two std::map instances instead of the bimap if you don't want the overhead of another library and learning curve. However, you have to be very careful that changes in one map's key or value get propagated to the map that works in the opposite direction so that things stay in sync.

In any case, my rule of thumb (and Qt's as well) is to decouple objects as much as possible. That's the purpose of signals and slots. No object instance knows who is listening to its signals, and no object instance knows who is calling its slots - the signal/slot pair is all that matters.

russdot
22nd April 2012, 16:19
Thank you very much for the reply, d_stranz.

What you've said makes a lot of sense! It adheres to both minimal coupling and also the Model-View-Controller design pattern that Qt enables & encourages. While I was trying to simplify the bookkeeping involved with my idea of inheriting from both QStandardItem and QGraphicsItem, it would have strongly coupled model & controller behaviour. I was hoping for a simpler/more elegant method of keeping track of pointers other than two hash maps, but I suppose it could be worse :)

russdot
24th April 2012, 00:09
What about using the same QStandardItemModel and QItemSelectionModel for both the QGraphicsView and QTreeView?

I was just examining the Chart Example (http://qt-project.org/doc/qt-4.8/itemviews-chart.html) and this seems to be how they achieve synchronization between the two views... Although I think I would have to have a much better understanding of how to insert/remove from the model and how QVariants work... Worth it? :confused:

Edit: Upon closer examination, the above idea won't work because QGraphicsView does not use a model like QAbstractItemView.

d_stranz
26th April 2012, 19:59
What about using the same QStandardItemModel and QItemSelectionModel for both the QGraphicsView and QTreeView?

As you say, since QGraphicsView does not use a model, you might have to invent some "glue" to accomplish that. But if you think about the idea of using a 2-way map between QStandardItem and QGraphicsItem, the synchronization problem is resolved pretty easily. When the user selects items from the tree, you retrieve the list of selected QStandardItems, look them up in the map, and set the matching QGraphicsItem to selected. Likewise, when the user selects items in the graphics view, you do the reverse lookup and set the QStandardItem as selected.

russdot
26th April 2012, 23:49
But if you think about the idea of using a 2-way map between QStandardItem and QGraphicsItem, the synchronization problem is resolved pretty easily. When the user selects items from the tree, you retrieve the list of selected QStandardItems, look them up in the map, and set the matching QGraphicsItem to selected. Likewise, when the user selects items in the graphics view, you do the reverse lookup and set the QStandardItem as selected.

Exactly. The solution I came up with is a templated bi-directional hash map:


template <class U, class V>
class BidirectionalHashMap
{
public:
U find(V v) { return mapVU.find(v).value(); }
V find(U u) { return mapUV.find(u).value(); }
void insert(U u, V v) { mapUV.insert(u,v); mapVU.insert(v,u); }

private:
QHash<U, V> mapUV;
QHash<V, U> mapVU;
};


It works great for what I needed to accomplish.

d_stranz
27th April 2012, 04:56
It works great for what I needed to accomplish.

At least as long as either of the QHash::find() calls in your two find() methods doesn't return QHash::end(). Then the following call to QHash::iterator::value() will assert. If your code guarantees that you will never try to find() a key that isn't in the map, this won't be a problem.

russdot
27th April 2012, 13:17
Ah, good point. Yes, I don't see any case where I would be trying to find a key that isn't in the bi-map.