Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Tree view boolean column bug

    Cinema 4D SDK
    python windows
    2
    2
    360
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • G
      Gregor M
      last edited by m_adam

      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)
      
      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by m_adam

        Hi @Gregor-M please make sure that your script is executable before sending it to us. Since in it's current state it does raise

        Traceback (most recent call last):
          File "scriptmanager", line 53, in GetDown
          File "scriptmanager", line 33, in GetChildren
        AttributeError: 'Entity' object has no attribute 'children'. Did you mean: 'GetChildren'?
        

        I added self.children = [] within the __init__ of Entity and it fixed the issue but this indicate that you are working on a different version than us. But even with it I'm not able to reproduce any issue and everything is working as expected.

        With that's said your else statement within the SetCheck looks very suspicious and I won't be surprised that the behavior you are experiencing is coming from this else statement.

        Finally note that you can call the function TreeViewCustomGui.SetRoot to define a root. Then the root argument that is available in all functions will be pointing to it.

        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        1 Reply Last reply Reply Quote 0
        • First post
          Last post