Help - PySide: Efficient way to write a QListWidget with editable QListWidgetItem?

1,157 views
Skip to first unread message

crazygamer

unread,
Jun 15, 2015, 6:33:03 PM6/15/15
to python_in...@googlegroups.com
Hi,
Am writing a renaming tool for maya. Its almost done but Im stuck on a issue.

I'm following Rob's book "Practical Maya Programming with Python". So I'm keeping the Qt(PySide) code seperate from my maya code.

Here's a quick laydown:

#The window contains two list widgets (old names, new names) and two buttons (load objects, rename objects). Old names list is not editable.
# The UI is made with Qt Designer and converted to python using pysideuic.

# The way its supposed to work:
1. Click "Load Objects" button to load selected objects(names) on both list widgets.
2. User edits the text on the "new names" list widget.
3. Click the "Rename Objects" button to rename objects on the "old names" widget with the naming from the "new names" widget.

It works the first time. After that, I get this error:

RuntimeError: Internal C++ object (PySide.QtGui.QListWidgetItem) already deleted.


I create the QListWidgetItem in the loop as I want the "new names" list to be editable. I can
understand qt/python clearing it as garbage collection. How can I create the QListWidgetItem properly so it doesn't get cleared on refresh.

Can someone help me understand and write this efficiently so I can keep the list editable and
refresh/recreate the QListWidgetItem on each object load. What I've written is quite crude.

The ui file(converted to python) is attached with this post.
Here's the extracted code: (It also contains a test function so you can test-run it with mayapy on a console)
renameTest.zip

Justin Israel

unread,
Jun 15, 2015, 6:46:35 PM6/15/15
to python_in...@googlegroups.com
Hey,

I'm not clear on why you have to keep your own independent newList of all the QListWidgetItems. Why not just pull them from the list widget as you do for the old ones? Then you can be sure you aren't holding on to stale objects when you want to access the text values. 

Justin


--
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/0d82af77-2068-4572-bef3-d20fecade05b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

crazygamer

unread,
Jun 16, 2015, 4:17:42 AM6/16/15
to python_in...@googlegroups.com
Thanks Justin for the quick reply.
I wanted the QListWidgetItems to be editable. Didn't know how to use the "setFlags" on the item, so I put each object as a QListWidgetItem, made it editable and added to the new list. I realize now it was not a good idea. Found another way to do that.

I have replaced this code:
for obj in load_objs:
    obj = QtGui.QListWidgetItem(obj, parent=self.ui.preview_new_names_list)
    obj.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable |
                 QtCore.Qt.ItemIsEnabled)
    self.ui.preview_new_names_list.addItem(obj)
    self.newList.append(obj)

With this:
for index in xrange(self.ui.preview_new_names_list.count()):
    item = self.ui.preview_new_names_list.item(index)
    item.setFlags(QtCore.Qt.ItemIsEditable |
                  QtCore.Qt.ItemIsEnabled |
                  QtCore.Qt.ItemIsSelectable)

It works now. No more stale objects.

There was another train of thought while implementing this. I was trying to find out a way to store object info instead of just the text. I'm using PyMel. I want to retain the actual object while adding the name to the lists. (To avoid duplicate named objects)
Is there a way I can handle duplicate named objects?
Can i store the actual object(in this case a pymel transform node) with the list and give it a title?

Currently I have it working by making another list which holds the actual objects when the objects are loaded in the lists by the "Load Objects" button.
loaded_objs = []

def load_objs():
    selected_objs = pmc.selected()
    objs_to_process = []
    for obj in selected_objs:
        obj_name = _get_short_name(obj)
        objs_to_process.append(obj_name)
        loaded_objs.append(obj)
    controller.objList.emit(objs_to_process)

def custom_rename(old_list, new_list):
    for i in range(len(old_list)):
        obj = pmc.PyNode(loaded_objs[i])
        _do_rename(obj, new_list[i])

The short name of the objects get added to the list while the object is in the "loaded_objs" variable.
(_do_rename is just a proc i made to rename objects with some error checking)
Is there a better way to do this?

-Harshad

To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.

Justin Israel

unread,
Jun 16, 2015, 6:10:05 AM6/16/15
to python_in...@googlegroups.com
On Tue, Jun 16, 2015 at 8:17 PM crazygamer <bari.h...@gmail.com> wrote:
Thanks Justin for the quick reply.
I wanted the QListWidgetItems to be editable. Didn't know how to use the "setFlags" on the item, so I put each object as a QListWidgetItem, made it editable and added to the new list. I realize now it was not a good idea. Found another way to do that.

I have replaced this code:
for obj in load_objs:
    obj = QtGui.QListWidgetItem(obj, parent=self.ui.preview_new_names_list)
    obj.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable |
                 QtCore.Qt.ItemIsEnabled)
    self.ui.preview_new_names_list.addItem(obj)
    self.newList.append(obj)

With this:
for index in xrange(self.ui.preview_new_names_list.count()):
    item = self.ui.preview_new_names_list.item(index)
    item.setFlags(QtCore.Qt.ItemIsEditable |
                  QtCore.Qt.ItemIsEnabled |
                  QtCore.Qt.ItemIsSelectable)

It works now. No more stale objects.

I think I didn't understand what you had meant by "editable", because I wasn't sure why you needed to save the items to another list, when you can just set the flags and then look up the item at any time. And thats what you ended up switching it to anyways, it looks like. So, cool, glad that worked out.
 

There was another train of thought while implementing this. I was trying to find out a way to store object info instead of just the text. I'm using PyMel. I want to retain the actual object while adding the name to the lists. (To avoid duplicate named objects)
Is there a way I can handle duplicate named objects?
Can i store the actual object(in this case a pymel transform node) with the list and give it a title?

Currently I have it working by making another list which holds the actual objects when the objects are loaded in the lists by the "Load Objects" button.
loaded_objs = []

def load_objs():
    selected_objs = pmc.selected()
    objs_to_process = []
    for obj in selected_objs:
        obj_name = _get_short_name(obj)
        objs_to_process.append(obj_name)
        loaded_objs.append(obj)
    controller.objList.emit(objs_to_process)

def custom_rename(old_list, new_list):
    for i in range(len(old_list)):
        obj = pmc.PyNode(loaded_objs[i])
        _do_rename(obj, new_list[i])

The short name of the objects get added to the list while the object is in the "loaded_objs" variable.
(_do_rename is just a proc i made to rename objects with some error checking)
Is there a better way to do this?

I would avoid started new lists to manage again, and trying to keep separate data in sync. You can create your own custom item type to add to your list if you want. Here is a super quick example of something you could do:
class MayaItem(QtGui.QListWidgetItem):

    USER_TYPE = QtGui.QListWidgetItem.UserType + 1

    def __init__(self, mayaItem, parent=None):
        super(MayaItem, self).__init__(parent, self.USER_TYPE)
        self._mayaItem = mayaItem

    def data(self, role):
        if role == QtCore.Qt.DisplayRole:
            return self._mayaItem.name()
        return super(MayaItem, self).data(role)
You can add that to your list and have it store your own object (a pymel object in this case), and then have the DisplayRole source some piece of data on the object for the text.
 
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.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/4814baa4-e9cb-4eb6-9ab5-b5e31dff8d1a%40googlegroups.com.

crazygamer

unread,
Jun 17, 2015, 4:30:59 AM6/17/15
to python_in...@googlegroups.com
That is exactly what I need. Learned something new today. Great!
One more thing though, I want to edit the text of the item. The text reverts back to original after edit. I am trying adding setData on the Custom item type with no success. Did search a hell lot on the Internet for an answer before posting here.

class CustomItem(QtGui.QListWidgetItem):

    USER_TYPE = QtGui.QListWidgetItem.UserType + 1

    def __init__(self, customitem, parent=None):
        super(CustomItem, self).__init__(parent, self.USER_TYPE)
        self.customItem = customitem

    def data(self, role):
        if role == QtCore.Qt.DisplayRole:
            return self.customItem.name()
        elif role == QtCore.Qt.EditRole:
            return self.customItem.name()
        return super(CustomItem, self).data(role)

    def setData(self, role, value):
        if role == QtCore.Qt.EditRole:
            self.setText(value)
            print value
            #self.setData(QtCore.Qt.DisplayRole, value)

The print command goes through, so setData is working. Is it possible to edit the text even after we set the data to return the object name?
Another solution would be to revert to a regular text list for the right-side listWidget that only contains names and not objects(custom type subclasses).

crazygamer

unread,
Jun 17, 2015, 11:21:16 AM6/17/15
to python_in...@googlegroups.com
Of course I can rename the object directly by doing the rename directly in setData:
    def setData(self, role, value):
        if role == QtCore.Qt.EditRole:
            self.customItem.rename(value)
Making the Display role also showing correctly as it should.
I'll have to remove the "Rename Objects" button as the rename is happening directly on edit.
 
Now that I have a custom item type in my ui (pymel node), I can do commands on the node directly(as above). Which I want to avoid if possible.
Till now I have kept the Qt code separate from Maya code. I have one file with the ui code and another file with maya code. They interact through the controller using signals. (These methods learnt from Practical Maya Programming in Python Book) I like the efficiency of keeping them separate
.
I have a test function in the ui code so I can run the ui on a console with mayapy and see how it works. Since I made a custom item now, i had to import pymel in the test function. This takes a few mini-seconds to load.
    def objs_list():
        from pymel.core import createNode
        #objs = []
        objs = [createNode('polyCube'),
                createNode('polySphere'),
                createNode('polyCone'),
                createNode('polyCylinder')]
        return objs

    def load_objs():
        controller.objList.emit(objs_list())

I have the tool working acceptable now. These questions are just to know what is the preferred, efficient or "pythonic" way to write.
Hope you can give me some inputs on how am I doing.
Thanks for taking the time to answer. :)
-Harshad

Justin Israel

unread,
Jun 17, 2015, 4:15:14 PM6/17/15
to python_in...@googlegroups.com
text()/setText() are convenience calls to data()/setData() with the DisplayRole. So when we reimplement data/setData to handle the DisplayRole, we don't then want to call setText(). We want to completely handle storing our value. So I think you are working against yourself a bit in that logic. 

But because you said that you don't want to directly work on the items, and instead just perform an action when the button is clicked, this makes it even easier. You don't even need the data/setData approach. All you really need to do is let the QListWidgetItem continue doing its own default behavior, but carry the custom item. You can then loop over all items when the button is clicked and do the operation:


That example lets you edit the items in the list. When you click the button, it does the rename on the custom item. 

--
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.

crazygamer

unread,
Jun 18, 2015, 7:12:05 AM6/18/15
to python_in...@googlegroups.com


On Thursday, June 18, 2015 at 1:45:14 AM UTC+5:30, Justin Israel wrote:
text()/setText() are convenience calls to data()/setData() with the DisplayRole. So when we reimplement data/setData to handle the DisplayRole, we don't then want to call setText(). We want to completely handle storing our value. So I think you are working against yourself a bit in that logic. 

But because you said that you don't want to directly work on the items, and instead just perform an action when the button is clicked, this makes it even easier. You don't even need the data/setData approach. All you really need to do is let the QListWidgetItem continue doing its own default behavior, but carry the custom item. You can then loop over all items when the button is clicked and do the operation:

 
Gosh I feel dumb. Why didnt I think of it myself. Reminds me to simplify ideas while drilling in complex code and getting lost.
 

That example lets you edit the items in the list. When you click the button, it does the rename on the custom item. 


Appreciate the comprehensive reply. Example was more than I asked for. :)
I understood the example by itself though I am having a bit of a problem implementing it in my code.

With the "setItem" method in the CustomItem class, I get this error while retrieving the item: 
item = self.ui.loaded_objs_list.item(index)
No object matches name: <scriptname.CustomItem object at 0x0000002AA0534EC8>

class CustomItem(QtGui.QListWidgetItem):

    USER_TYPE = QtGui.QListWidgetItem.UserType + 1

    def __init__(self, customitem, parent=None):
        super(CustomItem, self).__init__(parent, self.USER_TYPE)
        self.setItem(customitem)

    def setItem(self, customitem):
        self.setText(customitem.name().split('|')[-1])

I'm getting a reference to the object instead of the object. I figure its a syntax issue but could not fix it.

If I use the earlier method of adding a "customItem" attribute to the class, I'm able to retrieve it properly:
item = self.ui.loaded_objs_list.item(index).customItem

class CustomItem(QtGui.QListWidgetItem):

    USER_TYPE = QtGui.QListWidgetItem.UserType + 1

    def __init__(self, customitem, parent=None):
        super(CustomItem, self).__init__(parent, self.USER_TYPE)
        self.customItem = customitem
        self.setText(customitem.name().split('|')[-1])

I'm adding the objs to the lists like this:
xx = CustomItem(obj)
self.ui.loaded_objs_list.addItem(xx)
xy = CustomItem(obj)
self.ui.preview_new_names_list.addItem(xy)

###############################################################
Marking the above reply as Complete since my tool is working as expected.
The above question is just for my knowledge.

Thank you Justin for all the help.
-Harshad
 ##############################################################
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.

Justin Israel

unread,
Jun 18, 2015, 6:14:55 PM6/18/15
to python_in...@googlegroups.com
Are you able to pastebin a small reproduction of the problem? The example I posted was from a working bit of code that I wrote.

To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.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/30e7ef10-ec61-4bb2-8116-402d8a5c96b2%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages