[PyQt] Index in the model after selecting from QTreeView

2,507 views
Skip to first unread message

David Martinez

unread,
Jun 13, 2014, 6:41:00 AM6/13/14
to python_in...@googlegroups.com
Hi,

I've been experimenting with Model View Programming and so far, I've been able to create a QTreeView which displays the data of my custom model (Inheriting from 'QtCore.QAbstractItemModel'). I created my own custom kind of object to use within the model (as I'm storing some data about the items being stored). I use the 'internalPointer' method in the model to get a reference to my original object if I need it. I also have a 'QtGui.QSortFilterProxyModel' between the view and the model which allows me to filter and sort the results.

Everything seems to be working as expected but I'm struggling to get something working. I want to be able to right click an item on the treeView and get a popup menu which contains information about the specific item.

That means that I'm interested on the 'QModelI' value in the 'Model' for the element selected in the 'QTreeView' so I can retrieve the original node using 'internalPointer' and get the information that I need.

I'm not sure how to get the index in the original 'model' though. I have tried to use the 'indexAt(QPos)' method of the treeView but I guess that this is returning the index that it has within the view and not the model.

I'm not sure if I would need something like a 'selectionModel'. I started to try that but I think that it would give me the index of the 'QSortFilterProxyMode' instead of the one of 'QAbstractItemModel'.

Is there anything obvious that I'm missing?


Thanks in advance,



--
David Martinez - Technical Animator
 

Erkan Özgür Yılmaz

unread,
Jun 13, 2014, 6:56:15 AM6/13/14
to python_inside_maya

Use:

global_position = treeView.mapToGlobal(position)
index = treeView.indexAt(position)
model = treeView.model()
item = model.itemFromIndex(index)

Ozgur
eoyilmaz.blogspot.com

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/CAMLeNpwXVJS7Ko5_XUf3cTRML%2BpXBjt8h1Pm2EXz03HQaobR3Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Marcus Ottosson

unread,
Jun 13, 2014, 6:57:17 AM6/13/14
to python_in...@googlegroups.com
The item you're getting is indeed from the proxy model. You'll have to get it, and then map it to the original.


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/CAMLeNpwXVJS7Ko5_XUf3cTRML%2BpXBjt8h1Pm2EXz03HQaobR3Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Marcus Ottosson

unread,
Jun 13, 2014, 6:57:47 AM6/13/14
to python_in...@googlegroups.com
Looks like Erkan beat me to it. :)
--
Marcus Ottosson
konstr...@gmail.com

David Martinez

unread,
Jun 13, 2014, 7:11:29 AM6/13/14
to python_in...@googlegroups.com
It's working now! Thanks a lot!

Since I'm going through the proxy model, I had to use the 'mapToSource' method instead of the itemFromIndex.
The final code looks like this:

        global_position = self.uiTree.mapToGlobal(pos)
        index = self.uiTree.indexAt(pos)
        proxyModel = self.uiTree.model()
        item = proxyModel.mapToSource(index)
       
        node = item.internalPointer()



--
David Martinez - Technical Animator
 


Marcus Ottosson

unread,
Jun 13, 2014, 7:51:20 AM6/13/14
to python_in...@googlegroups.com
Glad to hear you got it working. :)



For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Marcus Ottosson

unread,
Jun 13, 2014, 8:05:50 AM6/13/14
to python_in...@googlegroups.com
If you're interested in MVC in general, I just finished up on an example of implementing MVC in Python, using Qt and it's QEvent and pyqtSignal frameworks, a custom Controller and two custom Views.

Maybe it could be useful in your exploration. :)


Best,
Marcus
--
Marcus Ottosson
konstr...@gmail.com

David Martinez

unread,
Jun 13, 2014, 8:08:09 AM6/13/14
to python_in...@googlegroups.com
Thanks a lot Marcus! I will definitely take a look.    :-)


--
David Martinez - Technical Animator
 


Marcus Ottosson

unread,
Jun 13, 2014, 8:11:19 AM6/13/14
to python_in...@googlegroups.com
To run it with PyQt4/PySide, you can import QtGui instead of QtWidgets, and do a string-replace of all occurrences. There isn't anything unique to PyQt5 in these examples, they should be backwards compatible. (if not, let me know!. And finally, PySide has the Signal class instead of the pyqtSignal of PyQt.

Best,
Marcus



For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Erkan Özgür Yılmaz

unread,
Jun 13, 2014, 8:18:52 AM6/13/14
to python_inside_maya

Thanks a lot for sharing this, it is a great source and a great example to the tons of things that I've to learn from you guys.

Ozgur
eoyilmaz.blogspot.com

Marcus Ottosson

unread,
Jun 13, 2014, 8:58:00 AM6/13/14
to python_in...@googlegroups.com
It's my pleasure, Erkan. I'm glad you like it.



For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Justin Israel

unread,
Jun 13, 2014, 4:17:16 PM6/13/14
to python_in...@googlegroups.com

Hey David,

I wanted to add that you should not use the internalPointer method outside of the internal code of your model. Normally in Qt land that would return a pointer to something outside components shouldn't be messing with but I know it feels natural to use it when you are setting your own objects. Probably better to write an abstraction around that as a method on your model.
What I tend to do is create custom role types and serve stuff through the data()  method.

Something like:

# class attribute on model
ObjectRole = QtCore.Qt.UserRole+1
...
def data(index, role):
    if role == self.ObjectRole:
        # resolve and return the object
   ....

That way you are covered if your model stores it's objects in a private list or dict or internalPointer. 

Something to keep an eye out for... I just recently had to fix a tricky bug where randomly my model would crash saying that various attributes didn't exist on a custom item I store in my model,  when they are part of the subclass implementation. Turned out that using internalPointer as the only place to store your object can lead to garbage collection issues and corruption. It would return broken instances of my items. I found similar reports of this online. Solution was to store my own mapping of the objects as well and manage adding and removing them properly, and then storing only a unique id as the internalPointer to look them up.
Thus was only one specific model that had this problem, as other models in my app were purely using internal lists and no internalPointer calls.

Also Erkan,  I think your answer was meant for a QTreeWidget where you are dealing with converting between indexes and model items.

Justin Israel

unread,
Jun 13, 2014, 4:20:59 PM6/13/14
to python_in...@googlegroups.com


On Jun 14, 2014 8:17 AM, "Justin Israel" <justin...@gmail.com> wrote:
>
> Also Erkan,  I think your answer was meant for a QTreeWidget where you are dealing with converting between indexes and model items.
>

Correction.  I should have said a QTreeView in combination with QStandardItemModel subclasses where it deals with items in the model. QAbstractItemModel is index like the view.

David Martinez

unread,
Jun 16, 2014, 5:08:22 AM6/16/14
to python_in...@googlegroups.com
That makes sense! Thanks Justin!

I will do the appropriate changes on my code so it does not use internalPointer anymore. I'll take it that 'QtCore.Qt.UserRole' returns the first index for custom roles and that I can create as many of them as I need, right?



--
David Martinez - Technical Animator
 


David Martinez

unread,
Jun 16, 2014, 5:25:35 AM6/16/14
to python_in...@googlegroups.com
Looking at your example, I have a couple of questions:

  • Are you using the role to return a reference to your object or returning the data that you were trying to retrieve from it? If you are doing the second thing, are you creating a series of user roles that return a few different values from the model? Is there a good reason to do one thing or the other?

  • Also, could you explain what do you mean by 'resolving' the object?

The way I see it, returning a reference to the object would allow me to do as many operations as I need with it without having to call the data method on the model each time.

Thanks in advance


--
David Martinez - Technical Animator
 


David Martinez

unread,
Jun 16, 2014, 5:31:40 AM6/16/14
to python_in...@googlegroups.com
Would it be OK to have a reference to 'internalPointer' within the model and return the result when the data method is called with the custom role?

I hope that I'm not missing anything obvious


Dave


--
David Martinez - Technical Animator
 


Justin Israel

unread,
Jun 16, 2014, 9:39:14 AM6/16/14
to python_in...@googlegroups.com
No you are right. The QtCore.Qt.UserRole is just the start of the custom value roles that you are allowed to use in your model, which guarantee not to conflict with Qt's own roles. So you can do stuff like this:

ObjectRole = QtCore.Qt.UserRole
IdRole = QtCore.Qt.UserRole+1
ColorRole = QtCore.Qt.UserRole+2
FooRole = QtCore.Qt.UserRole+3

And then you just pass those around when you call the data() method or in any function in the model/view that tasks an item role as a parameter. They are just numbers that you use as constants. And you can return anything you want, be it the entire object, or parts of an object for convenience. It is just a way for the model to know how to exactly get a desired value. An example of a useful reason for returning more specific data from your custom roles might be QSortFilterProxy, where it has methods for telling it which roles to use for filtering and sorting:


In this case, it is code that is within QSortFilterProxy, and you are just telling it where to get custom data to drive the class.
This is part of the interface design that allows views and models to generically be linked up. 




David Martinez

unread,
Jun 16, 2014, 4:03:10 PM6/16/14
to python_in...@googlegroups.com
Thanks again Justin, that makes sense

I've tweaked the code so the data method returns a reference to the object when using a custom role   :-)


--
David Martinez - Technical Animator
 


Reply all
Reply to author
Forward
0 new messages