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
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With