2

What I am currently trying to do is take a populated tree (qtreewidget) that has checkboxes at the bottom child level, and return the text of the path to the child if the box is checked. The reason I want to do this, is if a child is checked, it will then change a value in a key in a dictionary. (The "raw" dictionary the tree was created from). Here's a visual example of what I mean:

  1. From user input and server directory crawling, we have populated a tree that looks something like this: (Only the lowest level child items have checkboxes.) And sorry for the horrible tree diagram!! Hopefully it makes sense...

edited

study

-subject 1

--date

---[]c

---[]d

---[]e

-subject 2

--date

---[]g

---[]h

  1. If someone checks (for example) the "g" levle child, is there anyway to then get the path to "g" in a form something like [1, B, g] or 1-B-g or 1/B/g, etc.?

  2. One of the children levels (let's say in the example A and B) are also set to be user editable. So I'd need the info from the tree, not the info the tree was originally populated from.

I have tried printing self.ui.treeWidget indexes with no real luck in getting what I want. I feel as though there is an easy solution for this, but I can't seem to find it. Hopefully someone can help!

Actual Code Snippet:

    for h,study in enumerate(tree_dict['study']):
        study_name = study['study_name']
        treeSTUDY = QtGui.QTreeWidgetItem(self.ui.treeWidget, [study_name])
        treeSTUDY.setFlags(QtCore.Qt.ItemIsEnabled)
        self.ui.treeWidget.expandItem(treeSTUDY)            

        for i,subject in enumerate(study['subject']):
            subject = subject['ID']
            treeSUBJECT = QtGui.QTreeWidgetItem(treeSTUDY, [subject_id])
            treeSUBJECT.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled)

            for j,visit in enumerate(subject['visit']):
                scan_date = visit['date']                     
                treeDATE = QtGui.QTreeWidgetItem(treeSUBJECT, [scan_date[4:6])
                treeDATE.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled)

                for k,ser in enumerate(visit['series']):
                    s_name = ser['name'] + '-' + ser['description']
                    count =  str(ser['count'])
                    treeSCAN = QtGui.QTreeWidgetItem(treeDATE)
                    treeSCAN.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
                    treeSCAN.setCheckState(0, QtCore.Qt.Unchecked)   
                    treeSCAN.setText(0, s_name)
                    treeSCAN.setText(1, ser['time'])
                    treeSCAN.setText(2, ser['number'])
                    treeSCAN.setText(3, 'count')
eyllanesc
  • 235,170
  • 19
  • 170
  • 241

1 Answers1

3

All you need is a method that walks up the parent/child chain grabbing the text of each item until the parent is None:

def getTreePath(self, item):
    path = []
    while item is not None:
        path.append(str(item.text(0)))
        item = item.parent()
    return '/'.join(reversed(path))

UPDATE:

Here is a demo script that shows how to get the checked item and retrieve its path:

from PyQt4 import QtCore, QtGui

class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.tree = QtGui.QTreeWidget(self)
        self.tree.setHeaderHidden(True)
        for index in range(2):
            parent = self.addItem(self.tree, 'Item%d' % index)
            for color in 'Red Green Blue'.split():
                subitem = self.addItem(parent, color)
                for letter in 'ABC':
                    self.addItem(subitem, letter, True, False)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.tree)
        self.tree.itemChanged.connect(self.handleItemChanged)

    def addItem(self, parent, text, checkable=False, expanded=True):
        item = QtGui.QTreeWidgetItem(parent, [text])
        if checkable:
            item.setCheckState(0, QtCore.Qt.Unchecked)
        else:
            item.setFlags(
                item.flags() & ~QtCore.Qt.ItemIsUserCheckable)
        item.setExpanded(expanded)
        return item

    def handleItemChanged(self, item, column):
        if item.flags() & QtCore.Qt.ItemIsUserCheckable:
            path = self.getTreePath(item)
            if item.checkState(0) == QtCore.Qt.Checked:
                print('%s: Checked' % path)
            else:
                print('%s: UnChecked' % path)

    def getTreePath(self, item):
        path = []
        while item is not None:
            path.append(str(item.text(0)))
            item = item.parent()
        return '/'.join(reversed(path))

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 250, 450)
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for the reply, this definitely makes sense!! I think the one thing I still don't quite understand is how do I then make it only run on the child/path that is checked. I know how to use checkstate to add the if statement, but where in your example code would I specify the child? Isn't that needed so it doesn't just give me the path to every item on the tree? (I am new to python and QT so I apologize if this is a horribly simple/beginner question. ) – rocketstothemoon Mar 15 '14 at 15:53
  • @rocketstothemoon. I've added a demo script to my answer that shows how it all goes together. – ekhumoro Mar 15 '14 at 19:57
  • Amazing! I totally get it now. Thanks so much, it does exactly what I was looking for. – rocketstothemoon Mar 15 '14 at 21:30
  • I played around with your example and thought I understood it, but when I try to integrate it into my code, it doesn't work the same way. (This is very much my beginner knowledge level coming out). I have updated my post to include my actual code because I think my problem is that I'm populating my tree differently. My tree is being populated from a dictionary that returned earlier in the code "tree_dict". Any chance you can see why your example might not work with my tree? – rocketstothemoon Mar 16 '14 at 20:05
  • @rocketstothemoon. Well, no, because you haven't actually said what "doesn't work" ;-) – ekhumoro Mar 16 '14 at 20:37
  • haha I guess, in all honesty, my knowledge is what's broken. What I did tho was connect a button signal to handleItemChanged (rather than on checkstate change). I then replaced item with treeSTUDY (as it's my parent level item.) No result at all. Then I thought maybe since my parent level items aren't checkable, I was supposed to do the lowest child item. So I switched it with my treeSCAN column 0 item, and still nothing. When I tried printing path to see what the list had populated, it was the Qstring code rather than a normal string. :/ Confused. Appreciate your help a lot!! – rocketstothemoon Mar 16 '14 at 21:27
  • @rocketstothemoon. On the `QString`: I assume you're using python2, which means you won't get normal python strings by default. You can just do `string = unicode(qstring)` to get a python string object. I'm not sure what you mean by "connect a button signal to handleItemChanged". Can you post a more complete example (based on mine) so that I can see what's going wrong. – ekhumoro Mar 16 '14 at 22:17
  • Yes, I am using python 2.7 And unfortunately, I don't know how to give more info besides the code I edited in my original post. To make it exactly like your example, I'd have to completely re-write how the tree is populated (as you can see above). And simply trying to tweak your "getTreePath" function isn't working for me. I guess I'll keep trying, but I hope there's a way to do it without completely re-working the way the tree populates. I just can't seem to figure it out right now... – rocketstothemoon Mar 17 '14 at 01:36
  • @rocketstothemoon. I managed to adapt your code to my example quite easily, and it works fine for me. Just replace the for-loop in `Window.__init__` with your own loop (and change `self.ui.treeWidget` to `self.tree`) - there's no need to use my `addItem` method. There is also no need whatsoever to "tweak" the `getTreePath` method, as it will work the same way with any tree-widget item that is passed to it. Once you get my example working with your tree-data, it should be easier to see what is wrong with your real code. – ekhumoro Mar 17 '14 at 05:45
  • Oh geez! I got it working! I was making a really silly mistake. It was so easy once I saw it. Thanks for your help! I wish I could check your answer twice for all your help. – rocketstothemoon Mar 17 '14 at 21:14