PDA

View Full Version : Storing shared pointer in QModelIndex



d_stranz
9th September 2015, 18:02
I have a QAbstractItemModel-based class that wraps an external tree-shaped data structure. The nodes in the tree use boost::shared_ptr<> because multiple nodes may point to the same instance. (Think a property-value structure, where multiple nodes might have the property "units" with value "cm". The instance containing the "units"-"cm" pair is stored in a shared pointer).

I want to store these shared pointers in the QModelIndex instances I create using createIndex(). I am getting myself completely confused about how to do that.

I tried to use QModelIndex::internalPointer(), but C++ doesn't allow the shared_ptr to be cast to a void *. I also read another post here advising against using internalPointer().

I am guessing that the correct way is to use the model's data()/setData() or itemData()/setItemData() methods with a Qt::UserRole. I had already implemented the data() method on my model to return strings for the Qt::DisplayRole, but this was when I was using QModelIndex::internalPointer() and bare C++ pointers (before I changed the external model to use shared pointers).

The documentation for QAbstractItemModel::itemData() says
Reimplement this function if you want to extend the default behavior of this function to include custom roles in the map.

Fine, but what does that mean exactly? Do I need to implement my own data structure for storing the shared pointer in association with the QModelIndex instance?

I have been unsuccessful in my searching to find an example that does not use QStandardItem / QStandardItemModel as a basis.

Can someone please explain how this works (or point me to a link that does)?

ChrisW67
9th September 2015, 22:30
I would stick to the data() and setData() methods and ignore the others.

When setData() is given a QModelIndex for the Qt::UserRole it should store the supplied value in your model's internal data structure for the row/index/parent. In your case this could be a member variable of your shared pointer type, allowing it to be easily used internally by your model. You would convert the supplied QVariant to your pointer type to allow this.

When data() is given a QModelIndex for the Qt::UserRole it should return the stored value, wrapped as QVariant, from your model's internal data structure for the row/index/parent.

For this to work you will need to provide the appropriate mechanisms to allow your shared pointer to be wrapped as a QVariant.

anda_skoa
9th September 2015, 23:11
I am a bit confused on the mixup of QModelIndex::internalPointer() and data().

Those have entirely orthogonal use cases.

The internal pointer is an option for the model to store an internal reference in an index it hands out to a caller, so to later retrieve it if the caller wants something done with that index.

data(), on the other hand, hands out data that the model has access to.

Cheers,
_

d_stranz
10th September 2015, 02:40
I am a bit confused on the mixup of QModelIndex::internalPointer() and data().


Long ago when I was programming Windows with MFC, there were conventions where you could cast any pointer to void * and store it internally in an MFC object instance. This was part of the design, and was a convenient way to associate user data with a GUI element. My mistaken assumption was that the internalPointer() method is intended for the same use. It is confusing to me why this and internalId() are part of the public interface if they aren't intended for end-user use. Since most of Qt internals uses the letter-envelope paradigm to hide the internals from the user, it seems strange to me that this was not done the same way.

I am confused about data() / setData() because using them implies that I have to create yet another data structure that somehow associates each instance of my shared pointer with a specific QModelIndex. How else do I quickly locate the shared pointer for a given place in the tree? I don't want to be constantly traversing up the QModelIndex hierarchy and down my external tree structure every time I handle a data() call.

But QModelIndex instances are transient - I can't store them in a map. I could use QPersistentModelIndex instead, but I am not clear on when to create these and insert them into the map. I think I can do it like this:

In my override of QAbstractItemModel::index() I would


Check the map to see if requested index has already been added as a persistent index
If yes, return a QModelIndex via QPersistentModelIndex::operator const QModelIndex &()
If not, create a new QModelIndex (via createIndex()), wrap it with a QPersistentModelIndex and store that in the map along with the appropriate shared pointer.


In the QAbstractItemModel::data() method, I take the QModelIndex argument, wrap it in a QPersistentModelIndex, and search the map using that. From the match, I retrieve my shared pointer.

Does this sound like the right approach?

anda_skoa
10th September 2015, 09:35
Long ago when I was programming Windows with MFC, there were conventions where you could cast any pointer to void * and store it internally in an MFC object instance. This was part of the design, and was a convenient way to associate user data with a GUI element.

You can still use that, this is part of the C heritage. You can take the address of anything and any address can be casted to void*.
There are often better ways, e.g. storing a QVariant and let its type system and template magic handle the casting from/to void*.



My mistaken assumption was that the internalPointer() method is intended for the same use.

Well, yes and no.
The interalPointer is exactly that for the internal use of the model. Only a model can store the internal pointer in a QModelIndex, so only it knows what the void* actually points to.
Since it is tied to models, it can't be used to associate data with any GUI element.
Dynamic properties can be used for that.



It is confusing to me why this and internalId() are part of the public interface if they aren't intended for end-user use.

Well, half public.
Only models can store the internal pointer since the only public constructor of QModelIndex is the default constructor.
Only the QModelIndex' friend, QAbstractItemModel, an access the others.

I guess the API designers decided that making the getters only friend-accessible as well was too much code overhead, the values of internalId() or internalPointer() only mean something for the respective model anyway.



I am confused about data() / setData() because using them implies that I have to create yet another data structure that somehow associates each instance of my shared pointer with a specific QModelIndex. How else do I quickly locate the shared pointer for a given place in the tree? I don't want to be constantly traversing up the QModelIndex hierarchy and down my external tree structure every time I handle a data() call.

There are several ways this internal structure/lookup can work.
Additional to your suggestion, you could have a mapping from int to shared pointer, using the int as the internalId().
Or a list/set/whatever of shared pointers and put the pointer to such a shared pointer into the index via internalPointer().

Cheers,
_

d_stranz
10th September 2015, 16:20
Additional to your suggestion, you could have a mapping from int to shared pointer, using the int as the internalId().

So are you saying that internalId() is safe for a custom model implementation to use? I would much rather make a lookup map based on an int than use persistent indexes.

anda_skoa
10th September 2015, 17:22
The "internal" stuff in a QModelIndex is internal to the respective model instance.
I.e. any model function that gets a QModelIndex passed as an argument, can first check if model() is "this".
If it is, it can work with either internalId() or internalPointer() knowing that itself and nobody else put that info into the model index.

Also keep in mind that this lookup structure can be purged or at least cleaned in a lot of cases.

Anything that modifies the model, be it setData() or inserting/removing rows, etc. is assumed to invalidate all model indexes.
So any mapping that is kept for QModelndex -> internal data lookup could be invalidated at these times as well.
(might have to check for persistent model indexes though).

Event processing is also assumed to be invalidate model indexes, so one can also clear those mappings based on a timer.

Cheers,
_

d_stranz
10th September 2015, 18:58
My QAbstractItemModel that wraps my C++ tree structure is intended to be read-only (i.e. no editing, display only). When the external tree changes, I do a reset on the model (and will clear the smart pointer map). So any model indexes stored as persistent indexes will go away.

I guess I am interested in the more general case - what are best practices to use when mating an external data structure to a QAbstractItemModel? It seems that QStandardItemModel is about the only one that has built-in support for storing some reference to external user data into the model itself.

The most complete example of a custom tree model I have found is from Summerfield's "Advanced Qt Programming", and he uses internalPointer() to store pointers to his tree items. :(

anda_skoa
10th September 2015, 20:32
The QStandardItemModel actually stores the data it serves.
Usually this is not what you want, instead the QAIM subclass is used as an interface to the actual data.

Yes, it is a pity that QModelIndex cannot hold a QVariant as its reference.
But then again, a QVariant is basically just a void* plus type information and type information is what the model has implicitly since it knows what it puts into its indexes.
The only missing bit is that the model doesn't know when the model index gets destroyed so it can't use this for cleanup triggering and has to resort to different means.

Well, "only" because this is really annoying.

Cheers,
_