Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to parse a CheckboxTreeview from ttkwidgets and get the checkbox status of all the items in the tree

I've been trying to parse a checkbox treeview to return a dictionary with the item's ID as the key and the checkbox status as the value as "checked", "unchecked" and "tristate". However, I tried to sort the items by using CheckboxTreview.get_checked(). I don't know if i'm using it wrong or this is just a flaw from the package but it only returns the checked item from the highest level ('' as the parent).

from tkinter import *
from tkinter import ttk
from ttkwidgets import CheckboxTreeview

def parse_Tree(tree, parent):

    children = list(tree.get_children(parent))
    checkedList = tree.get_checked()
    itemDic = {}
    #print(checkedList)

    for item in children:
        if tree.get_children(item) == () and item in checkedList:
            itemDic[item] = "checked"
        elif tree.get_children(item) != () and item in checkedList:
            itemDic[item] = "checked"
            itemDic.update(parse_Tree(tree, item))
        elif tree.get_children(item) != () and item not in checkedList:
            for boxStatus in parse_Tree(tree, item).values():
                if boxStatus == "checked" or boxStatus == "tristate":
                    itemDic[item] = "tristate"
                else:
                    itemDic[item] = "unchecked"
                    itemDic.update(parse_Tree(tree,item))
        else:
            itemDic[item] = "unchecked"

    return itemDic

def listTreeview(textFile):

    list = []
    file = open(textFile, "r")
    treeview = file.read().split("\n")
    file.close()
    for item in treeview:
        list += [item.split(",")]
    root = Tk()
    master = ''
    level = []
    tree = CheckboxTreeview(root)
    for index,i in enumerate(list):
        indent = 0

        while i[0][indent] == ' ': indent += 1

        if indent%4:
            print("wrong indentation")
            break
        else:
            i[0] = i[0].replace(' ','')

        level.append(int(indent/4))

        if len(level)==1:
            tree.insert(master,'end',i[0], text = i[0])
        elif level[index]-level[index-1] == 1:
            master = list[index - 1][0]
            tree.insert(master, 'end', i[0], text=i[0])
        elif level[index]-level[index-1] < 0:
            prev = index-1
            while level[index] != level[prev]:
                prev -= 1
            master = tree.parent(list[prev][0])
            tree.insert(master,'end',i[0], text = i[0])
        elif level[index] - level[index - 1] > 1:
            print('wrong indentation')
        else: #level hasnt change
            tree.insert(master, 'end', i[0], text=i[0])
        if i[1] == '1':
            tree.change_state(i[0], "checked")


    tree.expand_all()
    dic = parse_Tree(tree,'')
    print(dic)
    tree.pack()
    root.mainloop()

listTreeview("Treeview.txt")

I parsed the following text file to work with, the indentation indicates the level and the last number indicates if it is checked or not.In this situation for exemple, item4.1.1 should appear as item4.1.1:"checked" but it doesn't...

Is there any other way to go through a checkbox treeview and get the state of each items?

item0,1
item1,0
    item1.1,0
    item1.2,0
item2,0
    item2.1,1
    item2.2,0
        item2.2.1,1
        item2.2.2,0
            item2.2.2.1,0
    item2.3,0
        item2.3.1,1
item3,1
item4,0
    item4.1,1
    item4.2,0
    item4.2.1,0
like image 713
Master3gg Avatar asked Sep 01 '25 10:09

Master3gg


1 Answers

The reason why tree.get_checked() does not return the expected result is that the parents of the lower level checked items are not checked and the code assumes that all the children of the unchecked parent are unchecked.

The shortcoming in the widget is that it propagates state changes only on user click, e.g. if the user checks an item, the item's parent becomes either checked or tristate. But this is not happening when you change the state of the items from the code.

What you can do is create methods that check/uncheck an item and propagates the state change:

from tkinter import Tk
from ttkwidgets import CheckboxTreeview as Tree


class CheckboxTreeview(Tree):

    def item_check(self, item):
        """Check item and propagate the state change to ancestors and descendants."""
        self._check_ancestor(item)
        self._check_descendant(item)

    def item_uncheck(self, item):
        """Uncheck item and propagate the state change to ancestors and descendants."""
        self._uncheck_descendant(item)
        self._uncheck_ancestor(item)

The _(un)check_ancestor() and _(un)check_descendant() are internal methods of the CheckboxTreeview which are used when the user clicks on an item.

So now, in listTreeview() you can use item_check() instead of change_state(), however, you need to replace

if i[1] == '1':
    tree.change_state(i[0], "checked")

by

if i[1] == '1':
    tree.item_check(i[0])
else:
    tree.item_uncheck(i[0])

because when the parent of a newly created item is checked, then by default the item is checked as well.

Now, the ancestors of the checked items in your tree have the right state and therefore tree.get_checked() will return the expected result.

screenshot

Alternative: If you don't want to propagate the items' states to their ancestors and keep your treeview like it is in your code, you can recursively look for checked items in the whole tree (and not only in the when the ancestor is checked / tristate):

def get_checked(tree):
    checked = []

    def rec_get_checked(item):
        if tree.tag_has('checked', item):
            checked.append(item)
        for ch in tree.get_children(item):
            rec_get_checked(ch)

    rec_get_checked('')
    return checked
like image 164
j_4321 Avatar answered Sep 06 '25 15:09

j_4321