Hello, I have a tree with checkboxes (image) and the following code. I would like to add 2 functions. The first function: When I click on "Test", the icon of that checkbox should change + all icons leading to the root of the tree, which also includes "Stromprüfung" and "LX 01 Gruppe635". The second function: When I select "LX 01 Gruppe635", all icons should change to checked, including "Sichtprüfung", "Stromprüfung", "Statistik", "Test", and "Test2".
def runAction(self, event):
"""
Fired whenever an item is clicked on this tree.
Arguments:
self: A reference to the component that is invoking this function.
event: An object with the following attributes:
data: The value of the contextual 'data' object on the clicked node
itemPath (list): A list containing the item indexes leading to the item
that was clicked.
label (str): Text to display for this option.
"""
# Folgende Variabeln dienen zur Überscihtlichkeit des Codes
Unclicked = "material/check_box_outline_blank" #Variable für Leere Checkbox
Clicked = "material/check_box" #Variable für ausgewählte Checkbox
itemsPath = event.itemPath #Liste, die den Pfad zu dem angeklickten Element enthält
items = self.props.items
for index in itemsPath[:-1]: #Überprüfe jedes Element in itemsPath außer dem letzten
items = items[index]["items"] #Navigiere zu den untergeordneten Elementen basierend auf dem Index
icon = items[itemsPath[-1]]["icon"]["path"] #Hole den Pfad des Icons des zuletzt angeklickten Elements
if icon == Unclicked: #Ändere den Pfad des Icons je nach aktuellem Zustand der Checkbox
items[itemsPath[-1]]["icon"]["path"] = Clicked
else:
items[itemsPath[-1]]["icon"]["path"] = Unclicked
You'll need to make a recursive function in your project library. To avoid having to walk through the tree structure twice, you'll need to put a boolean key in the data
key of each tree item to keep track of the item's check status (checked/unchecked).
You'll also need a recursive function to go through all the tree nodes and turn them into python objects, otherwise you get some weird behavior with setting values
Function to convert to python objects
def sanitizeIgnitionObject(element):
""" """
if hasattr(element, '__iter__'):
if hasattr(element, 'keys'):
return dict((k, sanitizeIgnitionObject(element[k])) for k in element.keys())
else:
return list(sanitizeIgnitionObject(x) for x in element)
return element
Recursive Function to set children as selected
CHECKED_BOX_ICON = "material/check_box"
UNCHECKED_BOX_ICON = "material/check_box_outline_blank"
def recursiveSetChecked(elements):
""" """
for element in elements:
element['icon']['path'] = CHECKED_BOX_ICON
element['data']['checked'] = True
if len(element['items']) == 0:
continue
element['items'] = recursiveSetChecked(element['items'])
return elements
Your event script would then look like something along the lines of:
Selection script in library
def treeItemSelected(items, itemPath, data):
""" """
# If item is being deselected, only change the icon and status bit
if data['checked']:
for itemIndex in itemPath[:-1]:
itemIndex = int(itemIndex)
items = items[itemIndex]['items']
item = items[itemPath[-1]]
item['data']['checked'] = False
item['icon']['path'] = UNCHECKED_BOX_ICON
# If item is being selected, set parents in path to selected, and set any children of the selected node to selected
else:
item = items
for idx, pathIdx in enumerate(itemPath):
pathIdx = int(pathIdx)
# First item in a tree is selected from a list
if idx == 0:
item = item[pathIdx]
# Subsequent items are selected from a list in a dict key
else:
item = item['items'][pathIdx]
item["icon"]["path"] = CHECKED_BOX_ICON
item['data']['checked'] = True
if len(item['items']) > 0:
# Set children to checked
item['items'] = recursiveSetChecked(item['items'])
return items
and you would call this from the actual runAction
with a single line:
def runAction(self, event):
"""
Fired whenever an item is clicked on this tree.
Arguments:
self: A reference to the component that is invoking this function.
event: An object with the following attributes:
data: The value of the contextual 'data' object on the clicked node
itemPath (list): A list containing the item indexes leading to the item
that was clicked.
label (str): Text to display for this option.
"""
self.props.items = LibNameHere.treeItemSelected(
sanitizeIgnitionObject(self.props.items), event.itemPath, event.data)
DISCLAIMER: Untested but based loosely off my own functions for interacting with tree items
Edit: Corrected the iteration through the event.itemPath and setting of values when unchecking an item.
I have written the functions "sanitizeIgnitionObject," "recursiveSetChecked," and "treeItemSelected" in my project library named "fkt_Komponentenbaum." I have created the Boolean key with false and true as shown in the image.
However, I am getting an error. Can you help me with that?
Here is the full script for the functions in "fkt_Komponentenbaum":
def sanitizeIgnitionObject(element):
if hasattr(element, '__iter__'):
if hasattr(element, 'keys'):
return dict((k, sanitizeIgnitionObject(element[k])) for k in element.keys())
else:
return list(sanitizeIgnitionObject(x) for x in element)
return element
CHECKED_BOX_ICON = "material/check_box"
UNCHECKED_BOX_ICON = "material/check_box_outline_blank"
def recursiveSetChecked(elements):
""" """
for element in elements:
element['icon']['path'] = CHECKED_BOX_ICON
element['data']['checked'] = True
if len(element['items']) == 0:
continue
element['items'] = recursiveSetChecked(element['items'])
return elements
def treeItemSelected(items, itemPath, data):
# If item is being deselected, only change the icon and status bit
if data['checked']:
for itemIndex in itemPath.split("/")[:-1]:
itemIndex = int(itemIndex)
items = items[itemIndex]['items']
items['data']['checked'] = False
items['icon']['path'] = UNCHECKED_BOX_ICON
# If item is being selected, set parents in path to selected, and set any children of the selected node to selected
else:
item = items
for idx, pathIdx in enumerate(itemPath.split("/")):
pathIdx = int(pathIdx)
# First item in a tree is selected from a list
if idx == 0:
item = item[pathIdx]
# Subsequent items are selected from a list in a dict key
else:
item = item['items'][pathIdx]
item["icon"]["path"] = CHECKED_BOX_ICON
item['data']['checked'] = True
if len(item['items']) > 0:
# Set children to checked
item['items'] = recursiveSetChecked(item['items'])
return items
for the ClickedOnItem event:
def runAction(self, event):
"""
Fired whenever an item is clicked on this tree.
Arguments:
self: A reference to the component that is invoking this function.
event: An object with the following attributes:
data: The value of the contextual 'data' object on the clicked node
itemPath (list): A list containing the item indexes leading to the item
that was clicked.
label (str): Text to display for this option.
"""
self.props.items = fkt_Komponentenbaum.treeItemSelected(fkt_Komponentenbaum.sanitizeIgnitionObject(self.props.items), event.itemPath, event.data)
It looks like itemPath is a list, not a string, so I imagine you will need to split the string in the next line:
# Iterate through the tagpaths
for idx, pathIdx in enumerate(itemPath):
# Get the last part of the tag path and cast it to an integer
pathIdx = int(pathIdx.split("/")[-1])
It looks like you are doing the same thing in line 31, but you're probably not faulting on it because data['checked'] isn't true.
1 Like
I have changed it as you have.
Now I get this error:
I guess item indexes are a list of long datatypes. The word path and split('\')
made me envision something like path\to\someIndex
.
This is probably good enough then:
for idx, pathIdx in enumerate(itemPath):
# First item in a tree is selected from a list
if idx == 0:
item = item[pathIdx]
#[...]
It seems that there is no need to split or cast anything.
1 Like
You are right! I have omitted .split and commented out pathIdx = int(pathIdx), and in line 31 there were the same issues, so I did the same there. It now works from unchecked to checked, but not the other way. However, I now have a new error in line 35.
Ah yes, the script I had modified was looking a what is returned from a tree's props.selection
property, which is a path/to/item
format. I made the bad assumption/didn't read that item.path
was a list.
change lines 35-36 to, include all three lines:
item = items[itemPath[-1]]
item['data']['checked'] = False
item['icon']['path'] = UNCHECKED_BOX_ICON
I forgot that in the first if
section I was assigning the list of items to a variable instead of a single item to a variable.
2 Likes
When I click on 'LX01 Group 635,' all lower points are checked, so the function is correct. However, if I click on a lower point like 'Test1,' then the entire tree disappears up to the clicked item.
Can this be fixed so that the tree remains visible?
This is the Code:
Please post formatted code - not pictures of code. Anyone who wants to test this or edit it for you would have to type it all out again or use OCR. (Use a picture as well if it provides context.) Thanks!
thanks for the tip. Here is the code for all the functions:
def sanitizeIgnitionObject(element):
if hasattr(element, '__iter__'):
if hasattr(element, 'keys'):
return dict((k, sanitizeIgnitionObject(element[k])) for k in element.keys())
else:
return list(sanitizeIgnitionObject(x) for x in element)
return element
CHECKED_BOX_ICON = "material/check_box"
UNCHECKED_BOX_ICON = "material/check_box_outline_blank"
def recursiveSetChecked(elements):
""" """
for element in elements:
element['icon']['path'] = CHECKED_BOX_ICON
element['data']['checked'] = True
if len(element['items']) == 0:
continue
element['items'] = recursiveSetChecked(element['items'])
return elements
def treeItemSelected(items, itemPath, data):
# If item is being deselected, only change the icon and status bit
if data['checked']:
for itemIndex in itemPath[:-1]: ########[:-1]
itemIndex = int(itemIndex)
items = items[itemIndex]['items']
item = items[itemPath[-1]]
item['data']['checked'] = False
item['icon']['path'] = UNCHECKED_BOX_ICON
# If item is being selected, set parents in path to selected, and set any children of the selected node to selected
else:
item = items
for idx, pathIdx in enumerate(itemPath): ########
#pathIdx = int(pathIdx)
# First item in a tree is selected from a list
if idx == 0:
item = item[pathIdx]
# Subsequent items are selected from a list in a dict key
else:
item = item['items'][pathIdx]
item["icon"]["path"] = CHECKED_BOX_ICON
item['data']['checked'] = True
if len(item['items']) > 0:
# Set children to checked
item['items'] = recursiveSetChecked(item['items'])
return items
The Code for the onItemClicked event:
def runAction(self, event):
"""
Fired whenever an item is clicked on this tree.
Arguments:
self: A reference to the component that is invoking this function.
event: An object with the following attributes:
data: The value of the contextual 'data' object on the clicked node
itemPath (list): A list containing the item indexes leading to the item
that was clicked.
label (str): Text to display for this option.
"""
self.props.items = fkt_Komponentenbaum.treeItemSelected(fkt_Komponentenbaum.sanitizeIgnitionObject(self.props.items), event.itemPath, event.data)
For fun, try changing
else:
item = items
for idx, pathIdx in enumerate(itemPath): ########
#pathIdx = int(pathIdx)
# First item in a tree is selected from a list
if idx == 0:
item = item[pathIdx]
to this:
else:
for idx, pathIdx in enumerate(itemPath):
# First item in a tree is selected from a list
if idx == 0:
item = items[pathIdx]
Might be running into something with how python does pass by reference for variables.
Also, for science, what happens if you select something like 'Sichtprûfung'?
1 Like
Thank you for all your help!
I have solved the problem, I needed a variable to navigate deeper into the tree without actually changing items. In the end, only the current icons and the checked flag in the items need to be changed.
I have replaced this part of the code:
if data['checked']:
for itemIndex in itemPath[:-1]: ########[:-1]
itemIndex = int(itemIndex)
items = items[itemIndex]['items']
item = items[itemPath[-1]]
item['data']['checked'] = False
item['icon']['path'] = UNCHECKED_BOX_ICON
with that part.
if data['checked']: #Wenn Icon checked
NavItems = items #Variable um tiefer in die Baumstruktur zu kommen ohne die Struktur zu verändern
for itemIndex in itemPath[:-1]: #durchlaufen des Pfad zum angeklicktem Item
itemIndex = int(itemIndex) #konventiert Index in eine ganze Zahl
NavItems = NavItems[itemIndex]['items'] #Aktualisiert Items
item = NavItems[itemPath[-1]] #Auswahl des angeklicktem Item
item['data']['checked'] = False #Setzt #Setzt checked flag auf false
item['icon']['path'] = UNCHECKED_BOX_ICON #Ändert Icon von Element auf nicht checked
Here is the full code in case someone needs a tree with checkboxes:
def sanitizeIgnitionObject(element):
"""Funktion zur Säuberung von Verschachtellungen"""
if hasattr(element, '__iter__'): #Prüfen, ob Element iteratierbar ist
if hasattr(element, 'keys'): #Prüfen, ob Element Keys besitzt
return dict((k, sanitizeIgnitionObject(element[k])) for k in element.keys()) #Dictuinary wird gesäubert
else:
return list(sanitizeIgnitionObject(x) for x in element) #Liste wird gesäubert
return element
CHECKED_BOX_ICON = "material/check_box" #Variable wenn Box Cheked
UNCHECKED_BOX_ICON = "material/check_box_outline_blank" #Variable wenn Box nicht Cheked
def recursiveSetChecked(elements):
"""Funktion um alle Icons unterhalb des angeklicktem Icon auf checked zu ändern.
Diese Funktion wird so oft durchlaufen, wie viele Items sich unterhalb des angeklicktem befinden """
for element in elements:
element['icon']['path'] = CHECKED_BOX_ICON #Ändert Icon von Element auf checked
element['data']['checked'] = True #Setzt checked flag auf true
if len(element['items']) == 0: #Schaut ob weiter Liste weiter verschachtelt ist
continue
element['items'] = recursiveSetChecked(element['items']) #durchläuft die Funktion nochmal
return elements
#system.perspective.print
def treeItemSelected(items, itemPath, data):
"""Funktion zur Rückverfolgung der Items. Es werden alle Icons bis zur wurzel auf Checked gesetzt"""
if data['checked']: #Wenn Icon checked
NavItems = items #Variable um tiefer in die Baumstruktur zu kommen ohne die Struktur zu verändern
for itemIndex in itemPath[:-1]: #durchlaufen des Pfad zum angeklicktem Item
itemIndex = int(itemIndex) #konventiert Index in eine ganze Zahl
NavItems = NavItems[itemIndex]['items'] #Aktualisiert Items
item = NavItems[itemPath[-1]] #Auswahl des angeklicktem Item
item['data']['checked'] = False #Setzt #Setzt checked flag auf false
item['icon']['path'] = UNCHECKED_BOX_ICON #Ändert Icon von Element auf nicht checked
else: #Wenn Icon nicht checked
item = items
for idx, pathIdx in enumerate(itemPath): #Enumerate: Nummeriert die Elemente in der Liste
if idx == 0: #Bedingung für das erste Element im Pfad
item = items[pathIdx] #Wählt das erste Element im Pfad aus
else:
item = item['items'][pathIdx] #Wählt das Element aus einer verschachtelten Ebene aus
item["icon"]["path"] = CHECKED_BOX_ICON #Ändert Icon von Element auf checked
item['data']['checked'] = True #Setzt checked flag auf true
if len(item['items']) > 0: #Prüft, ob das Element untergeordnete Elemente hat.
item['items'] = recursiveSetChecked(item['items']) #Ruft die Funktion recursiveSetChecked() rekursiv auf, um untergeordnete Elemente auszuwählen
return items