I have a problem with a tree view custom gui.
When I toggle any boolean field other than the bottom one it also toggles the top most one by itself.
import c4d
import weakref
# Control IDs
ID_TREEVIEW = 1000
ID_BTN_NEW_FOLDER = 1001
ID_BTN_DELETE = 1002
ID_PATHFIELD = 1003
# Tree column IDs
ID_CHECK = 1 # Checkbox column
ID_NAME = 2 # Name column
#---------------------------------------------------------------------
# Basic Entity
#---------------------------------------------------------------------
class Entity:
def __init__(self, is_root=False, name="Folder"):
self.is_root = is_root
self.name = name if not is_root else "Root"
self.checked = False
self.opened = True
self.selected = False
self.parent = None
def AddChild(self, child):
child.parent = weakref.ref(self)
self.children.append(child)
def GetChildren(self):
return self.children
def GetParent(self):
return self.parent() if self.parent else None
#---------------------------------------------------------------------
# TreeView Functions
#---------------------------------------------------------------------
class SimpleTreeFunctions(c4d.gui.TreeViewFunctions):
def __init__(self, dlg):
self._dlg = weakref.ref(dlg)
# Create the global Root entity.
self.root_entity = Entity(is_root=True)
# Top-level entities: Root and any added Folders (as siblings).
self.entities = [self.root_entity]
def GetFirst(self, root, userdata):
return self.entities[0] if self.entities else None
def GetDown(self, root, userdata, obj):
return obj.GetChildren()[0] if obj.GetChildren() else None
def GetNext(self, root, userdata, obj):
parent = obj.GetParent()
siblings = parent.GetChildren() if parent else self.entities
idx = siblings.index(obj) + 1
return siblings[idx] if idx < len(siblings) else None
def GetPred(self, root, userdata, obj):
parent = obj.GetParent()
siblings = parent.GetChildren() if parent else self.entities
idx = siblings.index(obj) - 1
return siblings[idx] if idx >= 0 else None
def GetId(self, root, userdata, obj):
return id(obj)
def Select(self, root, userdata, obj, mode):
# Clear all selections if a new selection is made.
if mode == c4d.SELECTION_NEW:
for e in self._AllEntities():
e.selected = False
if mode in [c4d.SELECTION_NEW, c4d.SELECTION_ADD]:
obj.selected = True
elif mode == c4d.SELECTION_SUB:
obj.selected = False
self._dlg().UpdatePathField()
def IsSelected(self, root, userdata, obj):
return obj.selected
def IsOpened(self, root, userdata, obj):
return obj.opened
def Open(self, root, userdata, obj, onoff):
obj.opened = onoff
# Toggle checkboxes.
def SetCheck(self, root, userdata, obj, column, checked, msg):
if column == ID_CHECK:
# If the object is global Root, only change when directly clicked.
if obj.is_root:
obj.checked = bool(checked)
else:
# When toggling a Folder, ensure the Root's state is preserved.
global_root = self.root_entity
original = global_root.checked
obj.checked = bool(checked)
global_root.checked = original
self._dlg()._treegui.Refresh()
def IsChecked(self, root, userdata, obj, column):
if obj.checked:
return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
return c4d.LV_CHECKBOX_ENABLED
def GetName(self, root, userdata, obj):
return obj.name
def SetName(self, root, userdata, obj, newname):
obj.name = newname
self._dlg().UpdatePathField()
return True
def _AllEntities(self):
# Recursively iterate through all entities.
def recurse(lst):
for ent in lst:
yield ent
yield from recurse(ent.GetChildren())
return recurse(self.entities)
def DeletePressed(self, root, userdata):
# Delete selected entities, except the global Root.
to_delete = [e for e in self._AllEntities() if e.selected and not e.is_root]
for e in to_delete:
parent = e.GetParent()
if parent:
parent.GetChildren().remove(e)
else:
self.entities.remove(e)
self._dlg().UpdatePathField()
return True
#---------------------------------------------------------------------
# Main Dialog: Contains the tree view, add and delete buttons, and a text field.
#---------------------------------------------------------------------
class SimpleTreeDialog(c4d.gui.GeDialog):
def __init__(self):
self.treeData = None
self._treegui = None
def CreateLayout(self):
self.SetTitle("Simple Tree (Folders as Siblings)")
# Buttons: Add Folder and Delete Selected.
self.GroupBegin(100, c4d.BFH_SCALEFIT, 2, 1)
self.AddButton(ID_BTN_NEW_FOLDER, c4d.BFH_SCALEFIT, name="Add Folder")
self.AddButton(ID_BTN_DELETE, c4d.BFH_SCALEFIT, name="Delete Selected")
self.GroupEnd()
# A text field to display a simple representation of the tree.
self.AddEditText(ID_PATHFIELD, c4d.BFH_SCALEFIT)
self.AddSeparatorH(0, c4d.BFH_SCALEFIT)
# Create the TreeView.
bc = c4d.BaseContainer()
bc.SetBool(c4d.TREEVIEW_BORDER, True)
self._treegui = self.AddCustomGui(ID_TREEVIEW, c4d.CUSTOMGUI_TREEVIEW, "",
c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 400, 300, bc)
if not self._treegui:
print("Error: Could not create TreeView")
return False
# Set up the tree layout: a checkbox and name column.
layout = c4d.BaseContainer()
layout.SetLong(ID_CHECK, c4d.LV_CHECKBOX)
layout.SetLong(ID_NAME, c4d.LV_TREE)
self._treegui.SetLayout(2, layout)
self._treegui.SetHeaderText(ID_CHECK, "Check")
self._treegui.SetHeaderText(ID_NAME, "Name")
# Initialize tree data: global Root is the first (and fixed) sibling.
self.treeData = SimpleTreeFunctions(self)
self._treegui.SetRoot(None, self.treeData, None)
self._treegui.Refresh()
self.UpdatePathField()
return True
def Command(self, id, msg):
if id == ID_BTN_NEW_FOLDER:
self.AddFolder()
elif id == ID_BTN_DELETE:
self.treeData.DeletePressed(None, None)
self._treegui.Refresh()
return True
def AddFolder(self):
# Create a new Folder entity and add it as a sibling (top-level).
new_entity = Entity(is_root=False, name="Folder")
self.treeData.entities.append(new_entity)
self._treegui.Refresh()
self.UpdatePathField()
def UpdatePathField(self):
# Build a simple string showing the names of top-level entities.
names = [ent.name for ent in self.treeData.entities]
self.SetString(ID_PATHFIELD, " | ".join(names))
if __name__=='__main__':
dlg = SimpleTreeDialog()
dlg.Open(c4d.DLG_TYPE_ASYNC, pluginid=0, defaultw=500, defaulth=400)