PyQt Tree Widget, adding check boxes for dynamic removal
Asked Answered
L

5

7

I am attempting to create a tree widget that will essentially allow the user to view various breakdowns of data and have the option to delete certain items. In order to do this I want to have check boxes associated with each top level item and each child so the user can select which top level items (and thus all the children of that top level item) to delete. Or which specific children to delete. To give you a better idea I've created an example where [x] represents a checked check box and [ ] represents an empty checkbox:

>Beverages Allowed in Stadium [ ]
    Soda                      [ ]
    Water                     [ ]
    Tea                       [ ]
    Spirits                   [X]
    Ale                       [ ]

>Tickets                      [X]
    Row A                     [X]
    Row B                     [X]
    Row C                     [X]
    Lawn                      [X]

Any suggestions how to implement this? I don't know if it makes a difference as far as difficulty, but i have allocated a separate column for the check box.

Loopy answered 10/7, 2015 at 13:38 Comment(0)
H
23

In addition to the answer you provided, you can simplify your logic by using the ItemIsTristate flag on the parent elements.

from PyQt4.QtCore import * 
from PyQt4.QtGui import * 
import sys

def main(): 
    app     = QApplication (sys.argv)
    tree    = QTreeWidget ()
    headerItem  = QTreeWidgetItem()
    item    = QTreeWidgetItem()

    for i in xrange(3):
        parent = QTreeWidgetItem(tree)
        parent.setText(0, "Parent {}".format(i))
        parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        for x in xrange(5):
            child = QTreeWidgetItem(parent)
            child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
            child.setText(0, "Child {}".format(x))
            child.setCheckState(0, Qt.Unchecked)
    tree.show() 
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

The three most important lines of code are:

parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)

This one sets up the parent element to be a three state check box.

child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
child.setCheckState(0, Qt.Unchecked)

These set up the child to be selectable and set the default to unchecked. If the child's checkbox isn't given a state, the checkbox element does not appear.


The code above builds a very simple tree.

Simple Tree

However, if I check a check box on the parent element, all the children are automatically selected:

Parent Selected

If, I wish to unselect a single child, the parent enters the partially selected (Tri-State):

TriState

If all children are unselected, the parent is automatically unselected. If the parent is unselected, all children are automatically unselected as well.

Hendry answered 10/7, 2015 at 14:4 Comment(6)
This seems to work well, however instead of getting a nice filled box, my boxes appear to just either have a horizontal dash through them or if i click it a second time it shows a check mark. Is this normal? Is there anyway I can either disable the solid dash or fix it to fill the entire box as in your example?Loopy
That is dependent on your OS and widget style. There is more documentation on that here, but is probably out of scope for this specific question.Hendry
Okay, Is there a way to just disable the whole fill and make it so it only has the check option?Loopy
If you only want it to be a binary checkbox and not a Tristate box, remove the Qt.ItemIsTristate flag and add parent.setCheckState(0, Qt.Unchecked) (again, a checkbox state is required for it to show). This will add the check box to the parent. You lose the automatic selection and deselection of children by removing the Tristate flag though.Hendry
is there any easy way to return a list of all the boxes that are currently checked? Aside from writing a loop that iterates through the entire tree checking every items checkstate?Loopy
@Loopy i'm using the following to retrieve checked children (and parents): root = treeWidget.invisibleRootItem() for i in range(root.childCount()): item = root.child(i) if item.checkState(0, QtCore.Qt.Checked): # do something or add to list (PS: i don't know how to format this so it looks nice)Egarton
T
8

I ported @andy's awesome example to PyQt5:

from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5.Qt import Qt
import sys

def main(): 
    app     = QtWidgets.QApplication(sys.argv)
    tree    = QtWidgets.QTreeWidget()
    headerItem  = QtWidgets.QTreeWidgetItem()
    item    = QtWidgets.QTreeWidgetItem()

    for i in range(3):
        parent = QtWidgets.QTreeWidgetItem(tree)
        parent.setText(0, "Parent {}".format(i))
        parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        for x in range(5):
            child = QtWidgets.QTreeWidgetItem(parent)
            child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
            child.setText(0, "Child {}".format(x))
            child.setCheckState(0, Qt.Unchecked)
    tree.show() 
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

screenshot

Taffrail answered 6/9, 2019 at 10:14 Comment(0)
L
4

QTreeWidgetItem actually has a built in check box that you can use fairly easily.

For example:

item = QTreeWidgetItem(self.treeWidget)

item.setCheckState(0, QtCore.Qt.Unchecked)
Loopy answered 10/7, 2015 at 13:52 Comment(1)
True, but your solution doesn't include the the hierarchical structure where the parent has a tristate that corresponds to its children's state. The example listed above does.Radiance
C
2

This is not an answer to your question, rather how to use the checkboxes once you have them. I used the example above from the answer marked as correct. It worked, but then when I tried to find how to know which checkboxes were marked, and I had a lot of issues. After a lot of searching I found a solution that worked for me, as I see there is no a lot of doc, so I want to leave a record for the future. Just to mention I used several solutions as invisibleRootItem in order to find the children of the parent but that didn't work.

I ended up using the class QTreeWidgetItemIterator with a flag QtGui.QTreeWidgetItemIterator.Checked in order to retreive the text of the checkboxes marked, and with that, I can continue working.

def vrfs_selected(self):
    iterator = QtGui.QTreeWidgetItemIterator(self.tree, QtGui.QTreeWidgetItemIterator.Checked)
    while iterator.value():
        item = iterator.value()
        print (item.text(0))    
        iterator += 1

the link to the documentation http://ftp.ics.uci.edu/pub/centos0/ics-custom-build/BUILD/PyQt-x11-gpl-4.7.2/doc/html/qtreewidgetitemiterator.html and an example https://riverbankcomputing.com/pipermail/pyqt/2014-May/034315.html

Customer answered 1/1, 2017 at 21:59 Comment(1)
This is the second part of the answerSahaptin
C
0

After scratching my head a lot on how to do this on PySide6 I would like to share how to do this on PySide6.

from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem
import sys

def main(): 
    app     = QApplication (sys.argv)
    tree    = QTreeWidget ()
    # headerItem  = QTreeWidgetItem()
    # item    = QTreeWidgetItem()
    for i in range(3):
        parent = QTreeWidgetItem(tree)
        parent.setText(0, "Parent {}".format(i))
        parent.setFlags(parent.flags() | Qt.ItemFlag.ItemIsAutoTristate | Qt.ItemFlag.ItemIsUserCheckable)
        for x in range(5):
            child = QTreeWidgetItem(parent)
            child.setFlags(child.flags() | Qt.ItemFlag.ItemIsUserCheckable)
            child.setText(0, "Child {}".format(x))
            child.setCheckState(0, Qt.CheckState.Unchecked)
    tree.show() 
    sys.exit(app.exec())

if __name__ == '__main__':
    main()

Credit to Andy and pklaus who lead the way.

Chancre answered 6/1 at 0:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.