How to set/ highlight QTreeView selection based on a given string

1,612 views
Skip to first unread message

kiteh

unread,
Sep 13, 2018, 7:25:40 PM9/13/18
to Python Programming for Autodesk Maya
I have the following QTreeView.
I am trying to set the selection in the QTreeview based on the string I have derived - eg. '/users/Alice/people' and so the highlighted cell should only be 'people' that is listed under the Alice level.

tree = {
'users': {
    "John" : ["graphics"],
    "Alice": ["book", "people"]
    }
}

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.tree_view = QtGui.QTreeView()
        self.setCentralWidget(self.tree_view)
        self.set_selection()

        self.model = QtGui.QStandardItemModel()
        self.populateTree(tree, self.model.invisibleRootItem())
        self.tree_view.setModel(self.model)
        self.tree_view.expandAll()

    def populateTree(self, children, parent):
        for child in children:
            child_item = QtGui.QStandardItem(child)
            parent.appendRow(child_item)
            if isinstance(children, dict):
                self.populateTree(children[child], child_item)

    def set_selection(self):
        to_set = "/user/Alice/people"
        view = self.tree_view.selectionModel()
        index = self.model.indexFromItem(to_set)
        view.select(QItemSelectionModel.Select|QItemSelectionModel.Rows)

        
win = MainWindow()
win.show()


Initially I thought that `findItems` may work but unfortunately that is only for QTreeWidget.
When I tried to do a `set_selection()`, I was prompted with an error that says "# AttributeError: 'NoneType' object has no attribute 'select' # ", it seems that the selectionModel is empty..

Michael Boon

unread,
Sep 14, 2018, 12:18:08 AM9/14/18
to Python Programming for Autodesk Maya
You're calling set_selection before you call self.tree_view.setModel. I would guess that is the problem.
Also I think you've missed "index" from the parameters you're passing to view.select()

kiteh

unread,
Sep 14, 2018, 12:30:21 PM9/14/18
to Python Programming for Autodesk Maya
Hey Michael, correct me if I am wrong, I will need to call for my set_selection case before using the setModel?

kiteh

unread,
Sep 14, 2018, 1:21:55 PM9/14/18
to Python Programming for Autodesk Maya
This is my piece of actual code - https://pastebin.com/pfqDgVtX

As soon as I tried adding the following after Line 292 (after `.setModel`) 
for row in range(self.tree_view.rowCount()):
    data.append([])
for column in range(self.tree_view.columnCount()):
    index = self.tree_view.index(row, column)
  data[row].append(str(self.tree_view.data(index).toString()))

I got this error:
# TypeError: rowCount() takes exactly 2 arguments (1 given) # 


Justin Israel

unread,
Sep 14, 2018, 5:41:48 PM9/14/18
to python_in...@googlegroups.com
You have mis-implemented rowCount, columnCount, and hasChildren signatures. They all take an optional parent index parameter which defaults to an invalid index meaning no parent. So you need to use index=None or index=QModelIndex() and handle that case. 

Some other notes... 

You may want to consider not constructing a handful of QIcon instances on every call to data(). It gets called alot. You can construct them once for the whole model and share them. Or at least construct them only when it's a decoration role. 

It is usually not a great idea to conditionally set private fields on classes such at the _node on your item class. That is the reason you have to handle exceptions later when accessing the name property. Just always set it to None or some appropriate default value so that your public members behave properly. Not having a node should still result in some empty string name. 

It might be a good idea to call the super() method at the end of functions like data() so that you get default behavior for any unhandled cases. 

And also, be careful about modifying the underlying parent/child stuff without informing the model. I know you begin and end row insertion for the root. I can't remember if it's ok to add tons of children without also doing nested begin/end row calls. If you end up seeing the initial state of your view not showing the plus sign for your root nodes then that could be why (until it calls hasChildren internally later to discover there are children) 

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/2c10335c-874c-494d-820f-7142980d725a%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

kiteh

unread,
Sep 14, 2018, 6:03:00 PM9/14/18
to Python Programming for Autodesk Maya
While I am not very adept in using PyQt yet, and this script that I have currently have on hand, is taken from a tool (with edited functions here and there).
I am wondering if you could advise, if it will be better for me to re-write the whole thing?

Truth be told, as mentioned, that I took the code from somewhere I am not entirely understanding the script very well.
Even so, if I am do planning to rewrite where need to have some of the features:

* Populates the tree from a given list of strings - example. "|ObjectA|ObjectB|ObjectC|..." such that the hierarchy is almost the same as the ones in the Outliner where you can collapse them
* Appends icon towards each item based on their node type

Will the above features be better/ easily implement using a QTreeWidget, rather than a QTreeView?
(I mention QTreeWidget as there seems to be more information for some other features I wanted, eg. for text/ cell color etc)

Justin Israel

unread,
Sep 15, 2018, 12:59:26 AM9/15/18
to python_in...@googlegroups.com
There wasn't anything hugely wrong with the existing code that warrants a total re-write. I was only pointing out a few adjustments that could be made.
The thing is though, the current implementation that you shared doesn't really seem to benefit from having a custom model. It seems like a lot of work is being done just to replicate a tree structure on your own, and then provide it to the abstract model to serve to a tree view. Using an abstract model makes more sense when you already have an existing data structure or data source that you want to wrap. For instance, you could store basically nothing and show the dag just by doing api queries as needed, when the model asks what is at a certain level or under a certain parent. When you decide to do an abstract model, you have to correctly handle a bunch of method implementations. It can also be beneficial to use a custom model when you start hitting performance limitations of the item-based approach.
Honestly you could just be using either a QTreeWidget, or a QTreeView + QStandardItemModel. Especially if you are starting out with Qt. They already provide the parent/child items that you are replicating. QTreeWidget is meant for a really easy interface to just adding items. If you end up needing more fine-grained control then you can do the view/model approach. The goal you want to achieve can be done with either one. I knocked together a quick example of how you can do this with QTreeWidget:


It can just as easily be switched to view/model instead. I didn't do the part with the icons, but that is just a matter of calling item.setIcon(0, theIcon).
Let me know if you have any questions about this code.

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.

Justin Israel

unread,
Sep 15, 2018, 1:03:43 AM9/15/18
to python_in...@googlegroups.com
I forgot to add a couple code comments that I think would be helpful:

# Break a path like "|root|one|two" into parts and reverse:
# ["|root", "|root|one", "|root|one|two"]
parts = [path.rsplit('|', i)[0] for i in xrange(path.count('|'))]
parts.reverse()

# Get the last name component of the path: 
# "|root|one" -> "one"
name = part.rsplit('|', 1)[-1]


Michael Boon

unread,
Sep 16, 2018, 11:03:57 PM9/16/18
to Python Programming for Autodesk Maya
In the code in your original post, set_selection sets the view's selection based on indices from the model. As far as I can tell, you will need to populate the model and connect it to the view before you do that. I would expect set_selection to be called after all other setup is complete except perhaps for signal connections.

The code in your pastebin is very different from the code above though, so maybe this is irrelevant now?

kiteh

unread,
Sep 18, 2018, 8:25:23 PM9/18/18
to Python Programming for Autodesk Maya
Hi Justin,

Many thanks for getting back to me on this.

I do have some questions (kindly pardon my questions).

1. Why is there a need to use `enumerate`? It seems that the variable - `i` is not being used anywhere.. If so, wouldn't it be better to use/ write it as
for part in (parts):
   
...

2. I am still not quite understanding the usage of `PATH_ROLE` or `self.PATH_ROLE`? Especially the latter not being declared anywhere before it has been used?

3. Lastly, about the `itemFromPath()`, what is the argument - `parent`? 

Justin Israel

unread,
Sep 18, 2018, 10:13:17 PM9/18/18
to python_in...@googlegroups.com
On Wed, Sep 19, 2018 at 12:25 PM kiteh <kiteh...@gmail.com> wrote:
Hi Justin,

Many thanks for getting back to me on this.

I do have some questions (kindly pardon my questions).

1. Why is there a need to use `enumerate`? It seems that the variable - `i` is not being used anywhere.. If so, wouldn't it be better to use/ write it as
for part in (parts):
   
...


You can remove that and just use "for part in parts". I forgot to revert than line when I had removed some test code that was using it. 

2. I am still not quite understanding the usage of `PATH_ROLE` or `self.PATH_ROLE`? Especially the latter not being declared anywhere before it has been used?

PATH_ROLE has been declared as a class variable on line 11. Thus it is accessible as a member on the class or instances of that class. You could make it a global to the whole module if you wanted. But it is a bit tidier to scope the constants with the class that is using them.
 

3. Lastly, about the `itemFromPath()`, what is the argument - `parent`? 

`parent` is the parent QTreeWidgetItem where you want to start searching. Otherwise it defaults to starting from the root of the tree, recursively.
 

--
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.
Reply all
Reply to author
Forward
0 new messages