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

    Treeview Column adjust

    Cinema 4D SDK
    python r20 r25
    2
    8
    1.4k
    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.
    • M
      mogh
      last edited by

      Dear developers,

      is there a solution for displaying the columns in the right width when the treeview is populated with data?
      I can get the width of each cell with geUserArea.DrawGetTextWidth("somestring"), but I do not know how to "readjust" the columns to the right width ...

      class listview():
      
          def GetColumnWidth(self, root, userdata, obj, col, area):
              return 40
      
      def DrawCell():
          
          geUserArea.DrawGetTextWidth("somestring")
      

      current status see the overlapping on the date
      2022-02-21 08_20_34-Cad File Importer 0.66.png
      desired output
      2022-02-21 08_20_58-Cad File Importer 0.66.png

      regarding code, it builds on the treeview list floating around here in the forums. I do not know of a SDK Example?

      thanks in advance

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @mogh
        last edited by ferdinand

        Hello @mogh,

        Thank you for reaching out to us. I really must stress here again that we require executable code to be posted, as lined out in our Forum Guidelines. While there are sometimes questions which can be answered abstractly, both the users and we ourselves usually prefer concrete answers, i.e., something executable. When users do not provide code, doing this will be much harder for us, as we have then not only to write that code, but also do a lot of guessing.

        About your question:

        You must measure the objects that are passed to GetColumnWidth. The method also passes a GeUserArea which is conveniently setup to measure string widths. I have used an older tree view example written by Maxime (see end of the posting for the full listing of the modified code) to demonstrate the approach. The core of the example is its method GetColumnWidth:

        def GetColumnWidth(self, root, userdata, obj, col, area):
            """Measures the width of cells.
        
            Although this function is called #GetColumnWidth and has a #col, it is
            not only executed by column but by cell. So, when there is a column
            with items requiring the width 5, 10, and 15, then there is no need
            for evaluating all items. Each item can return its ideal width and
            Cinema 4D will then pick the largest value.
        
            Args:
                root (any): The root node of the tree view.
                userdata (any): The user data of the tree view.
                obj (any): The content of the current cell.
                col (int): The index of the column #obj is contained in.
                area (GeUserArea): An already initialized GeUserArea to measure
                 the width of strings.
            
            Returns:
                TYPE: Description
            """
            # The default width of a column is 80 units.
            width = 80
            # For all calls for cells which contain a string item ...
            if isinstance(obj, str):
                # Replace the width with the text width. area is a prepopulated
                # user area which has already setup all the font stuff, we can
                # measure right away.
                width = area.DrawGetTextWidth(obj)
            return width
        

        If this does not help in your case, we must ask for a concrete code example. As a side note - in our C++ API GUIs like yours are a much less labor-intensive task, since list views and their accompanying helper class SimpleListView can be used there.

        Cheers,
        Ferdinand

        """Example for setting the column width of a tree view by the width of its
        items.
        
        The only changes which have been made in comparison to the original code can
        be found in TextureObject.__init__(), TextureObject.RandomString() and 
        ListView.GetColumnWidth(). The tree view items #TextureObject now generate
        random #texturePath and #otherData strings and the treeview scales its columns
        to the length of these strings.
        
        Adapted from:
            https://developers.maxon.net/forum/topic/10654/14102_using-customgui-listview/2
        """
        import c4d
        import random
         
        # Be sure to use a unique ID obtained from [URL-REMOVED]
        PLUGIN_ID = 1000010 # TEST ID ONLY
         
        # TreeView Column IDs.
        ID_CHECKBOX = 1
        ID_NAME = 2
        ID_OTHER = 3
        
        # A tuple of characters to select from (a-z).
        CHARACTERS = tuple(chr (n) for n in range(97, 122))
         
        class TextureObject(object):
            """
            Class which represent a texture, aka an Item in our list
            """
            def __init__(self):
                self.texturePath = TextureObject.RandomString(5, 10)
                self.otherData = TextureObject.RandomString(10, 20)
                self._selected = False
        
            @staticmethod
            def RandomString(minLength: int, maxLength: int) -> str:
                """Returns a string of random characters with a length between 
                #minLength and #maxLength.
        
                The characters are taken from the 97 (a) to 122 (z) ASCII range.
                """
                return "".join(
                    (random.choice(CHARACTERS) for _ in range(minLength, maxLength)))
         
            @property
            def IsSelected(self):
                return self._selected
         
            def Select(self):
                self._selected = True
         
            def Deselect(self):
                self._selected = False
         
            def __repr__(self):
                return str(self)
         
            def __str__(self):
                return self.texturePath
         
         
        class ListView(c4d.gui.TreeViewFunctions):
         
            def __init__(self):
                """
                """
                # Add ten mock data texture objects.
                self.listOfTexture = [TextureObject() for _ in range(10)]
         
         
            def IsResizeColAllowed(self, root, userdata, lColID):
                return True
         
            def IsTristate(self, root, userdata):
                return False
         
            def GetColumnWidth(self, root, userdata, obj, col, area):
                """Measures the width of cells.
        
                Although this function is called #GetColumnWidth and has a #col, it is
                not only executed by column but by cell. So, when there is a column
                with items requiring the width 5, 10, and 15, then there is no need
                for evaluating all items. Each item can return its ideal width and
                Cinema 4D will then pick the largest value.
        
                Args:
                    root (any): The root node of the tree view.
                    userdata (any): The user data of the tree view.
                    obj (any): The content of the current cell.
                    col (int): The index of the column #obj is contained in.
                    area (GeUserArea): An already initialized GeUserArea to measure
                     the width of strings.
                
                Returns:
                    TYPE: Description
                """
                # The default width of a column is 80 units.
                width = 80
                # For all calls for cells which contain a string item ...
                if isinstance(obj, str):
                    # Replace the width with the text width. area is a prepopulated
                    # user area which has already setup all the font stuff, we can
                    # measure right away.
                    width = area.DrawGetTextWidth(obj)
                return width
        
            def IsMoveColAllowed(self, root, userdata, lColID):
                # The user is allowed to move all columns.
                # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
                return True
         
            def GetFirst(self, root, userdata):
                """
                Return the first element in the hierarchy, or None if there is no element.
                """
                rValue = None if not self.listOfTexture else self.listOfTexture[0]
                return rValue
         
            def GetDown(self, root, userdata, obj):
                """
                Return a child of a node, since we only want a list, we return None everytime
                """
                return None
         
            def GetNext(self, root, userdata, obj):
                """
                Returns the next Object to display after arg:'obj'
                """
                rValue = None
                currentObjIndex = self.listOfTexture.index(obj)
                nextIndex = currentObjIndex + 1
                if nextIndex < len(self.listOfTexture):
                    rValue = self.listOfTexture[nextIndex]
         
                return rValue
         
            def GetPred(self, root, userdata, obj):
                """
                Returns the previous Object to display before arg:'obj'
                """
                rValue = None
                currentObjIndex = self.listOfTexture.index(obj)
                predIndex = currentObjIndex - 1
                if 0 <= predIndex < len(self.listOfTexture):
                    rValue = self.listOfTexture[predIndex]
         
                return rValue
         
            def GetId(self, root, userdata, obj):
                """
                Return a unique ID for the element in the TreeView.
                """
                return hash(obj)
         
            def Select(self, root, userdata, obj, mode):
                """
                Called when the user selects an element.
                """
                if mode == c4d.SELECTION_NEW:
                    for tex in self.listOfTexture:
                        tex.Deselect()
                    obj.Select()
                elif mode == c4d.SELECTION_ADD:
                    obj.Select()
                elif mode == c4d.SELECTION_SUB:
                    obj.Deselect()
         
            def IsSelected(self, root, userdata, obj):
                """
                Returns: True if *obj* is selected, False if not.
                """
                return obj.IsSelected
         
            def SetCheck(self, root, userdata, obj, column, checked, msg):
                """
                Called when the user clicks on a checkbox for an object in a
                `c4d.LV_CHECKBOX` column.
                """
                if checked:
                    obj.Select()
                else:
                    obj.Deselect()
         
            def IsChecked(self, root, userdata, obj, column):
                """
                Returns: (int): Status of the checkbox in the specified *column* for *obj*.
                """
                if obj.IsSelected:
                    return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
                else:
                    return c4d.LV_CHECKBOX_ENABLED
         
            def GetName(self, root, userdata, obj):
                """
                Returns the name to display for arg:'obj', only called for column of type LV_TREE
                """
                return str(obj) # Or obj.texturePath
         
            def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
                """
                Draw into a Cell, only called for column of type LV_USER
                """
                if col == ID_OTHER:
                    name = obj.otherData
                    geUserArea = drawinfo["frame"]
                    w = geUserArea.DrawGetTextWidth(name)
                    h = geUserArea.DrawGetFontHeight()
                    xpos = drawinfo["xpos"]
                    ypos = drawinfo["ypos"] + drawinfo["height"]
                    drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
         
            def DoubleClick(self, root, userdata, obj, col, mouseinfo):
                """
                Called when the user double-clicks on an entry in the TreeView.
         
                Returns:
                  (bool): True if the double-click was handled, False if the
                    default action should kick in. The default action will invoke
                    the rename procedure for the object, causing `SetName()` to be
                    called.
                """
                c4d.gui.MessageDialog("You clicked on " + str(obj))
                return True
         
            def DeletePressed(self, root, userdata):
                "Called when a delete event is received."
                for tex in reversed(self.listOfTexture):
                    if tex.IsSelected:
                        self.listOfTexture.remove(tex)
         
        class TestDialog(c4d.gui.GeDialog):
            _treegui = None # Our CustomGui TreeView
            _listView = ListView() # Our Instance of c4d.gui.TreeViewFunctions
         
            def CreateLayout(self):
                # Create the TreeView GUI.
                customgui = c4d.BaseContainer()
                customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN)
                customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True) # True if the tree view may have a header line.
                customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False) # True if no lines should be drawn.
                customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) # True if the user can move the columns.
                customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) # True if the column width can be changed by the user.
                customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) # True if all lines have the same height.
                customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) # Alternate background per line.
                customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True) # True if cursor keys should be processed.
                customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False) # Suppresses the rename popup when the user presses enter.
         
                self._treegui = self.AddCustomGui(1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 300, customgui)
                if not self._treegui:
                    print ("[ERROR]: Could not create TreeView")
                    return False
         
                self.AddButton(1001, c4d.BFH_CENTER, name="Add")
                return True
         
            def InitValues(self):
                # Initialize the column layout for the TreeView.
                layout = c4d.BaseContainer()
                layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
                layout.SetLong(ID_NAME, c4d.LV_TREE)
                layout.SetLong(ID_OTHER, c4d.LV_USER)
                self._treegui.SetLayout(3, layout)
         
                # Set the header titles.
                self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
                self._treegui.SetHeaderText(ID_NAME, "Name")
                self._treegui.SetHeaderText(ID_OTHER, "Other")
                self._treegui.Refresh()
         
                # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
                self._treegui.SetRoot(self._treegui, self._listView, None)
                return True
         
            def Command(self, id, msg):
                # Click on button
                if id == 1001:
                    # Add data to our DataStructure (ListView)
                    newID = len(self._listView.listOfTexture) + 1 
                    tex = TextureObject("T{}".format(newID))
                    self._listView.listOfTexture.append(tex)
         
                    # Refresh the TreeView
                    self._treegui.Refresh()
         
                return True
         
         
        class MenuCommand(c4d.plugins.CommandData):
            dialog = None
         
            def Execute(self, doc):
                if self.dialog is None:
                    self.dialog = TestDialog()
                return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
         
            def RestoreLayout(self, sec_ref):
                if self.dialog is None:
                    self.dialog = TestDialog()
                return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
         
         
        def main():
            c4d.plugins.RegisterCommandPlugin(
                PLUGIN_ID, "Python TreeView Example", 0, None, "Python TreeView Example", MenuCommand())
         
        if __name__ == "__main__":
            main()
        

        [URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • M
          mogh
          last edited by mogh

          I'am having Forum troubles posting ... is there a max char limit ?

          Replying here with a long answer with code gives me "Error: error"

          kind regrads
          mogh

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @mogh
            last edited by

            Hey @mogh,

            Sorry for the inconvenience caused, but I probably know the issue. Try posting your content without the code snippet. I have encountered the problem twice myself, here and in another thread. My code above is formatted in a very specific way as otherwise this problem will occur.

            The line:

            CHARACTERS = tuple(chr (n) for n in range(97, 122))
            

            Is very much intentional in this form, as removing the whitespace between chr and ( will raise the problem of the super descriptive error "error" ;). This also applies to normal text, you cannot have the characters chr dircetly followed by (.

            If the problem is caused by another section, please just use something like pastebin for now and I will have a look what causes the problem.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • M
              mogh
              last edited by

              @ferdinand Thank you again, and I am sorry, for not following the guidelines, need to print them out I guess. Also forgot to "Ask a Question"

              I understand the concept (or perhaps not at all) but even in your example it only takes the string of the "first" column (because obj always returns its "name" not its other variables) and after putting a print inside the isinstance() I figured its never trigered ?

              I modified the code so the button inserts a long string after the treeview was established to illustrate it.

              Regarding Cloumn -> I guess I have to evaluate the column and then get the coresponding variable from the object to measure the right width?

                      print(obj, col, area.DrawGetTextWidth(obj))
              
                              if isinstance(obj, str):        
                                  # Replace the width with the text width. area is a prepopulated        
                                  # user area which has already setup all the font stuff, we can        
                                  # measure right away.        
                                  width = area.DrawGetTextWidth(obj)        
                                  print(width)        
                              return width
              

              Full code with working Button:

              """Example for setting the column width of a tree view by the width of its
              items.
              
              The only changes which have been made in comparison to the original code can
              be found in TextureObject.__init__(), TextureObject.RandomString() and 
              ListView.GetColumnWidth(). The tree view items #TextureObject now generate
              random #texturePath and #otherData strings and the treeview scales its columns
              to the length of these strings.
              
              Adapted from:
                  https://developers.maxon.net/forum/topic/10654/14102_using-customgui-listview/2
              """
              import c4d
              import random
               
              # Be sure to use a unique ID obtained from [URL-REMOVED]
              PLUGIN_ID = 1000010 # TEST ID ONLY
               
              # TreeView Column IDs.
              ID_CHECKBOX = 1
              ID_NAME = 2
              ID_OTHER = 3
              ID_LONGFILENAME = 4
              
              # A tuple of characters to select from (a-z).
              CHARACTERS = tuple( chr ( n) for n in range(97, 122))
              
              
              class TextureObject(object):
                  """
                  Class which represent a texture, aka an Item in our list
                  """
                  def __init__(self):
                      self.texturePath = TextureObject.RandomString(5, 10)
                      self.otherData = TextureObject.RandomString(5, 20)
                      self.longfilename = "-"
                      self._selected = False
              
                  @staticmethod
                  def RandomString(minLength: int, maxLength: int) -> str:
                      """Returns a string of random characters with a length between 
                      #minLength and #maxLength.
              
                      The characters are taken from the 97 (a) to 122 (z) ASCII range.
                      """
                      return "".join(
                          (random.choice(CHARACTERS) for _ in range(minLength, maxLength)))
               
                  @property
                  def IsSelected(self):
                      return self._selected
               
                  def Select(self):
                      self._selected = True
               
                  def Deselect(self):
                      self._selected = False
               
                  def __repr__(self):
                      return str(self)
               
                  def __str__(self):
                      return self.texturePath
               
               
              class ListView(c4d.gui.TreeViewFunctions):
               
                  def __init__(self):
                      """
                      """
                      # Add ten mock data texture objects.
                      self.listOfTexture = [TextureObject() for _ in range(10)]
              
                  def IsResizeColAllowed(self, root, userdata, lColID):
                      return True
               
                  def IsTristate(self, root, userdata):
                      return False
               
                  def GetColumnWidth(self, root, userdata, obj, col, area):
                      """Measures the width of cells.
              
                      Although this function is called #GetColumnWidth and has a #col, it is
                      not only executed by column but by cell. So, when there is a column
                      with items requiring the width 5, 10, and 15, then there is no need
                      for evaluating all items. Each item can return its ideal width and
                      Cinema 4D will then pick the largest value.
              
                      Args:
                          root (any): The root node of the tree view.
                          userdata (any): The user data of the tree view.
                          obj (any): The content of the current cell.
                          col (int): The index of the column #obj is contained in.
                          area (GeUserArea): An already initialized GeUserArea to measure
                           the width of strings.
                      
                      Returns:
                          TYPE: Description
                      """
                      # The default width of a column is 80 units.
                      width = 80
                      # For all calls for cells which contain a string item ...
                      print(obj, col, area.DrawGetTextWidth(obj))
              
                      if isinstance(obj, str):
                          # Replace the width with the text width. area is a prepopulated
                          # user area which has already setup all the font stuff, we can
                          # measure right away.
                          width = area.DrawGetTextWidth(obj)
                          print(width)
                      return width
              
                  def IsMoveColAllowed(self, root, userdata, lColID):
                      # The user is allowed to move all columns.
                      # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
                      return True
               
                  def GetFirst(self, root, userdata):
                      """
                      Return the first element in the hierarchy, or None if there is no element.
                      """
                      rValue = None if not self.listOfTexture else self.listOfTexture[0]
                      return rValue
               
                  def GetDown(self, root, userdata, obj):
                      """
                      Return a child of a node, since we only want a list, we return None everytime
                      """
                      return None
               
                  def GetNext(self, root, userdata, obj):
                      """
                      Returns the next Object to display after arg:'obj'
                      """
                      rValue = None
                      currentObjIndex = self.listOfTexture.index(obj)
                      nextIndex = currentObjIndex + 1
                      if nextIndex < len(self.listOfTexture):
                          rValue = self.listOfTexture[nextIndex]
               
                      return rValue
               
                  def GetPred(self, root, userdata, obj):
                      """
                      Returns the previous Object to display before arg:'obj'
                      """
                      rValue = None
                      currentObjIndex = self.listOfTexture.index(obj)
                      predIndex = currentObjIndex - 1
                      if 0 <= predIndex < len(self.listOfTexture):
                          rValue = self.listOfTexture[predIndex]
               
                      return rValue
               
                  def GetId(self, root, userdata, obj):
                      """
                      Return a unique ID for the element in the TreeView.
                      """
                      return hash(obj)
               
                  def Select(self, root, userdata, obj, mode):
                      """
                      Called when the user selects an element.
                      """
                      if mode == c4d.SELECTION_NEW:
                          for tex in self.listOfTexture:
                              tex.Deselect()
                          obj.Select()
                      elif mode == c4d.SELECTION_ADD:
                          obj.Select()
                      elif mode == c4d.SELECTION_SUB:
                          obj.Deselect()
               
                  def IsSelected(self, root, userdata, obj):
                      """
                      Returns: True if *obj* is selected, False if not.
                      """
                      return obj.IsSelected
               
                  def SetCheck(self, root, userdata, obj, column, checked, msg):
                      """
                      Called when the user clicks on a checkbox for an object in a
                      `c4d.LV_CHECKBOX` column.
                      """
                      if checked:
                          obj.Select()
                      else:
                          obj.Deselect()
               
                  def IsChecked(self, root, userdata, obj, column):
                      """
                      Returns: (int): Status of the checkbox in the specified *column* for *obj*.
                      """
                      if obj.IsSelected:
                          return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
                      else:
                          return c4d.LV_CHECKBOX_ENABLED
               
                  def GetName(self, root, userdata, obj):
                      """
                      Returns the name to display for arg:'obj', only called for column of type LV_TREE
                      """
                      return str(obj)  # Or obj.texturePath
               
                  def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
                      """
                      Draw into a Cell, only called for column of type LV_USER
                      """
                      if col == ID_OTHER:
                          name = obj.otherData
                          geUserArea = drawinfo["frame"]
                          w = geUserArea.DrawGetTextWidth(name)
                          h = geUserArea.DrawGetFontHeight()
                          xpos = drawinfo["xpos"]
                          ypos = drawinfo["ypos"] + drawinfo["height"]
                          drawinfo["frame"].DrawText(name, xpos, ypos - int(h * 1.1))
              
                      if col == ID_LONGFILENAME:
                          name = obj.longfilename
                          geUserArea = drawinfo["frame"]
                          w = geUserArea.DrawGetTextWidth(name)
                          h = geUserArea.DrawGetFontHeight()
                          xpos = drawinfo["xpos"]
                          ypos = drawinfo["ypos"] + drawinfo["height"]
                          drawinfo["frame"].DrawText(name, xpos, ypos - int(h * 1.1))
               
                  def DoubleClick(self, root, userdata, obj, col, mouseinfo):
                      """
                      Called when the user double-clicks on an entry in the TreeView.
               
                      Returns:
                        (bool): True if the double-click was handled, False if the
                          default action should kick in. The default action will invoke
                          the rename procedure for the object, causing `SetName()` to be
                          called.
                      """
                      c4d.gui.MessageDialog("You clicked on " + str(obj))
                      return True
               
                  def DeletePressed(self, root, userdata):
                      "Called when a delete event is received."
                      for tex in reversed(self.listOfTexture):
                          if tex.IsSelected:
                              self.listOfTexture.remove(tex)
              
              
              class TestDialog(c4d.gui.GeDialog):
                  _treegui = None  # Our CustomGui TreeView
                  _listView = ListView()  # Our Instance of c4d.gui.TreeViewFunctions
               
                  def CreateLayout(self):
                      # Create the TreeView GUI.
                      customgui = c4d.BaseContainer()
                      customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN)
                      customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True)  # True if the tree view may have a header line.
                      customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, True)  # True if no lines should be drawn.
                      customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True)  # True if the user can move the columns.
                      customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True)  # True if the column width can be changed by the user.
                      customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True)  # True if all lines have the same height.
                      customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True)  # Alternate background per line.
                      customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True)  # True if cursor keys should be processed.
                      customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False)  # Suppresses the rename popup when the user presses enter.
               
                      self._treegui = self.AddCustomGui(1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 300, customgui)
                      if not self._treegui:
                          print("[ERROR]: Could not create TreeView")
                          return False
               
                      self.AddButton(1001, c4d.BFH_CENTER, name="Add")
                      return True
               
                  def InitValues(self):
                      # Initialize the column layout for the TreeView.
                      layout = c4d.BaseContainer()
                      layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
                      layout.SetLong(ID_NAME, c4d.LV_TREE)
                      layout.SetLong(ID_LONGFILENAME, c4d.LV_USER)
                      layout.SetLong(ID_OTHER, c4d.LV_USER)
              
                      self._treegui.SetLayout(4, layout)
               
                      # Set the header titles.
                      self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
                      self._treegui.SetHeaderText(ID_NAME, "Name")
                      self._treegui.SetHeaderText(ID_LONGFILENAME, "Long Filename")
                      self._treegui.SetHeaderText(ID_OTHER, "Other")
              
                      self._treegui.Refresh()
               
                      # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
                      self._treegui.SetRoot(self._treegui, self._listView, None)
                      return True
               
                  def Command(self, id, msg):
                      # Click on button
                      if id == 1001:
                          # Add data to our DataStructure (ListView)
                          newID = int(len(self._listView.listOfTexture) + 1)
                          # print(newID)
                          # newID = "T{}".format(newID)
                          # print(newID)
                          # tex = TextureObject(newID)
                          tex = TextureObject()
                          tex.texturePath = "Some new data"
                          tex.longfilename = TextureObject.RandomString(20, 80)
                          self._listView.listOfTexture.append(tex)
                          # Refresh the TreeView
                          self._treegui.Refresh()
               
                      return True
               
               
              class MenuCommand(c4d.plugins.CommandData):
                  dialog = None
               
                  def Execute(self, doc):
                      if self.dialog is None:
                          self.dialog = TestDialog()
                      return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
               
                  def RestoreLayout(self, sec_ref):
                      if self.dialog is None:
                          self.dialog = TestDialog()
                      return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
               
               
              def main():
                  c4d.plugins.RegisterCommandPlugin(
                      PLUGIN_ID, "Python TreeView Example", 0, None, "Python TreeView Example", MenuCommand())
              
              
              if __name__ == "__main__":
                  main()
              
              

              thank you
              mogh


              [URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.

              ferdinandF 1 Reply Last reply Reply Quote 0
              • ferdinandF
                ferdinand @mogh
                last edited by ferdinand

                Hey @mogh,

                I made a little mistake in my method, you do not get passed in the cell item but the list view item for the cell as obj, i.e., the TextureObject in this case. So, you must pick the right attribute to evaluate which will fix the problem. The correct code is (based on your listing):

                    def GetColumnWidth(self, root, userdata, obj, col, area):
                        """Measures the width of cells.
                
                        Although this function is called #GetColumnWidth and has a #col, it is
                        not only executed by column but by cell. So, when there is a column
                        with items requiring the width 5, 10, and 15, then there is no need
                        for evaluating all items. Each item can return its ideal width and
                        Cinema 4D will then pick the largest value.
                
                        Args:
                            root (any): The root node of the tree view.
                            userdata (any): The user data of the tree view.
                            obj (any): The item for the current cell.
                            col (int): The index of the column #obj is contained in.
                            area (GeUserArea): An already initialized GeUserArea to measure
                             the width of strings.
                        
                        Returns:
                            TYPE: Description
                        """
                        # The default width of a column is 80 units.
                        width = 80
                
                        if col == ID_NAME:
                            return area.DrawGetTextWidth(obj.texturePath) + 5
                        if col == ID_OTHER:
                            return area.DrawGetTextWidth(obj.otherData) + 5
                        if col == ID_LONGFILENAME:
                            return area.DrawGetTextWidth(obj.longfilename) + 5
                            
                        return width
                

                I first went into the wrong direction and started poking around in the cell drawing in DrawCell, where I added the ...- thing when something is too short for the content. It is not required, but I left it in. Find the full code below.

                Cheers,
                Ferdinand

                The result (I intentionally scaled down the column "Other" to show the "..." thing):
                4e5841c8-2539-4b71-80f9-69332fafa48b-image.png

                The code:

                """Example for setting the column width of a tree view by the width of its
                items.
                
                The only changes which have been made in comparison to the original code can
                be found in TextureObject.__init__(), TextureObject.RandomString() and 
                ListView.GetColumnWidth(). The tree view items #TextureObject now generate
                random #texturePath and #otherData strings and the treeview scales its columns
                to the length of these strings.
                
                Adapted from:
                    https://developers.maxon.net/forum/topic/10654/14102_using-customgui-listview/2
                """
                import c4d
                import random
                 
                # Be sure to use a unique ID obtained from [URL-REMOVED]
                PLUGIN_ID = 1000010 # TEST ID ONLY
                 
                # TreeView Column IDs.
                ID_CHECKBOX = 1
                ID_NAME = 2
                ID_OTHER = 3
                ID_LONGFILENAME = 4
                
                # A tuple of characters to select from (a-z).
                CHARACTERS = tuple( chr ( n) for n in range(97, 122))
                
                
                class TextureObject(object):
                    """
                    Class which represent a texture, aka an Item in our list
                    """
                    def __init__(self):
                        self.texturePath = TextureObject.RandomString(5, 10)
                        self.otherData = TextureObject.RandomString(5, 20)
                        self.longfilename = "-"
                        self._selected = False
                
                    @staticmethod
                    def RandomString(minLength: int, maxLength: int) -> str:
                        """Returns a string of random characters with a length between 
                        #minLength and #maxLength.
                
                        The characters are taken from the 97 (a) to 122 (z) ASCII range.
                        """
                        return "".join(
                            (random.choice(CHARACTERS) for _ in range(minLength, maxLength)))
                 
                    @property
                    def IsSelected(self):
                        return self._selected
                 
                    def Select(self):
                        self._selected = True
                 
                    def Deselect(self):
                        self._selected = False
                 
                    def __repr__(self):
                        return str(self)
                 
                    def __str__(self):
                        return self.texturePath
                 
                 
                class ListView(c4d.gui.TreeViewFunctions):
                 
                    def __init__(self):
                        """
                        """
                        # Add ten mock data texture objects.
                        self.listOfTexture = [TextureObject() for _ in range(10)]
                
                    def IsResizeColAllowed(self, root, userdata, lColID):
                        return True
                 
                    def IsTristate(self, root, userdata):
                        return False
                 
                    def GetColumnWidth(self, root, userdata, obj, col, area):
                        """Measures the width of cells.
                
                        Although this function is called #GetColumnWidth and has a #col, it is
                        not only executed by column but by cell. So, when there is a column
                        with items requiring the width 5, 10, and 15, then there is no need
                        for evaluating all items. Each item can return its ideal width and
                        Cinema 4D will then pick the largest value.
                
                        Args:
                            root (any): The root node of the tree view.
                            userdata (any): The user data of the tree view.
                            obj (any): The item for the current cell.
                            col (int): The index of the column #obj is contained in.
                            area (GeUserArea): An already initialized GeUserArea to measure
                             the width of strings.
                        
                        Returns:
                            TYPE: Description
                        """
                        # The default width of a column is 80 units.
                        width = 80
                        # Replace the width with the text width. area is a prepopulated
                        # user area which has already setup all the font stuff, we can
                        # measure right away.
                
                        if col == ID_NAME:
                            return area.DrawGetTextWidth(obj.texturePath) + 5
                        if col == ID_OTHER:
                            return area.DrawGetTextWidth(obj.otherData) + 5
                        if col == ID_LONGFILENAME:
                            return area.DrawGetTextWidth(obj.longfilename) + 5
                            
                        return width
                
                    def IsMoveColAllowed(self, root, userdata, lColID):
                        # The user is allowed to move all columns.
                        # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
                        return True
                 
                    def GetFirst(self, root, userdata):
                        """
                        Return the first element in the hierarchy, or None if there is no element.
                        """
                        rValue = None if not self.listOfTexture else self.listOfTexture[0]
                        return rValue
                 
                    def GetDown(self, root, userdata, obj):
                        """
                        Return a child of a node, since we only want a list, we return None everytime
                        """
                        return None
                 
                    def GetNext(self, root, userdata, obj):
                        """
                        Returns the next Object to display after arg:'obj'
                        """
                        rValue = None
                        currentObjIndex = self.listOfTexture.index(obj)
                        nextIndex = currentObjIndex + 1
                        if nextIndex < len(self.listOfTexture):
                            rValue = self.listOfTexture[nextIndex]
                 
                        return rValue
                 
                    def GetPred(self, root, userdata, obj):
                        """
                        Returns the previous Object to display before arg:'obj'
                        """
                        rValue = None
                        currentObjIndex = self.listOfTexture.index(obj)
                        predIndex = currentObjIndex - 1
                        if 0 <= predIndex < len(self.listOfTexture):
                            rValue = self.listOfTexture[predIndex]
                 
                        return rValue
                 
                    def GetId(self, root, userdata, obj):
                        """
                        Return a unique ID for the element in the TreeView.
                        """
                        return hash(obj)
                 
                    def Select(self, root, userdata, obj, mode):
                        """
                        Called when the user selects an element.
                        """
                        if mode == c4d.SELECTION_NEW:
                            for tex in self.listOfTexture:
                                tex.Deselect()
                            obj.Select()
                        elif mode == c4d.SELECTION_ADD:
                            obj.Select()
                        elif mode == c4d.SELECTION_SUB:
                            obj.Deselect()
                 
                    def IsSelected(self, root, userdata, obj):
                        """
                        Returns: True if *obj* is selected, False if not.
                        """
                        return obj.IsSelected
                 
                    def SetCheck(self, root, userdata, obj, column, checked, msg):
                        """
                        Called when the user clicks on a checkbox for an object in a
                        `c4d.LV_CHECKBOX` column.
                        """
                        if checked:
                            obj.Select()
                        else:
                            obj.Deselect()
                 
                    def IsChecked(self, root, userdata, obj, column):
                        """
                        Returns: (int): Status of the checkbox in the specified *column* for *obj*.
                        """
                        if obj.IsSelected:
                            return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
                        else:
                            return c4d.LV_CHECKBOX_ENABLED
                 
                    def GetName(self, root, userdata, obj):
                        """
                        Returns the name to display for arg:'obj', only called for column of type LV_TREE
                        """
                        return str(obj)  # Or obj.texturePath
                 
                    def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
                        """
                        Draw into a Cell, only called for column of type LV_USER
                        """
                        if col in (ID_OTHER, ID_LONGFILENAME):
                            text = obj.otherData if col == ID_OTHER else obj.longfilename
                            canvas = drawinfo["frame"]
                            textWidth = canvas.DrawGetTextWidth(text)
                            textHeight = canvas.DrawGetFontHeight()
                            xpos = drawinfo["xpos"]
                            ypos = drawinfo["ypos"] + drawinfo["height"]
                
                            if (drawinfo["width"] < textWidth):
                                while (drawinfo["width"] < textWidth):
                                    if len(text) <= 4:
                                        text = "..."
                                        break
                                    text = text[:-4] + "..."
                                    textWidth = canvas.DrawGetTextWidth(text)
                
                            textWidth = canvas.DrawGetTextWidth(text)
                            drawinfo["frame"].DrawText(text, xpos, ypos - int(textHeight * 1.1))
                 
                    def DoubleClick(self, root, userdata, obj, col, mouseinfo):
                        """
                        Called when the user double-clicks on an entry in the TreeView.
                 
                        Returns:
                          (bool): True if the double-click was handled, False if the
                            default action should kick in. The default action will invoke
                            the rename procedure for the object, causing `SetName()` to be
                            called.
                        """
                        c4d.gui.MessageDialog("You clicked on " + str(obj))
                        return True
                 
                    def DeletePressed(self, root, userdata):
                        "Called when a delete event is received."
                        for tex in reversed(self.listOfTexture):
                            if tex.IsSelected:
                                self.listOfTexture.remove(tex)
                
                
                class TestDialog(c4d.gui.GeDialog):
                    _treegui = None  # Our CustomGui TreeView
                    _listView = ListView()  # Our Instance of c4d.gui.TreeViewFunctions
                 
                    def CreateLayout(self):
                        # Create the TreeView GUI.
                        customgui = c4d.BaseContainer()
                        customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN)
                        customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True)  # True if the tree view may have a header line.
                        customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, True)  # True if no lines should be drawn.
                        customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True)  # True if the user can move the columns.
                        customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True)  # True if the column width can be changed by the user.
                        customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True)  # True if all lines have the same height.
                        customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True)  # Alternate background per line.
                        customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True)  # True if cursor keys should be processed.
                        customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False)  # Suppresses the rename popup when the user presses enter.
                 
                        self._treegui = self.AddCustomGui(1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 300, customgui)
                        if not self._treegui:
                            print("[ERROR]: Could not create TreeView")
                            return False
                 
                        self.AddButton(1001, c4d.BFH_CENTER, name="Add")
                        return True
                 
                    def InitValues(self):
                        # Initialize the column layout for the TreeView.
                        layout = c4d.BaseContainer()
                        layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
                        layout.SetLong(ID_NAME, c4d.LV_TREE)
                        layout.SetLong(ID_LONGFILENAME, c4d.LV_USER)
                        layout.SetLong(ID_OTHER, c4d.LV_USER)
                        self._layout = layout
                        self._treegui.SetLayout(4, layout)
                 
                        # Set the header titles.
                        self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
                        self._treegui.SetHeaderText(ID_NAME, "Name")
                        self._treegui.SetHeaderText(ID_LONGFILENAME, "Long Filename")
                        self._treegui.SetHeaderText(ID_OTHER, "Other")
                
                        self._treegui.Refresh()
                 
                        # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
                        self._treegui.SetRoot(self._treegui, self._listView, None)
                        return True
                 
                    def Command(self, id, msg):
                        # Click on button
                        if id == 1001:
                            # Add data to our DataStructure (ListView)
                            newID = int(len(self._listView.listOfTexture) + 1)
                            # print(newID)
                            # newID = "T{}".format(newID)
                            # print(newID)
                            # tex = TextureObject(newID)
                            tex = TextureObject()
                            tex.texturePath = "Some new data"
                            tex.longfilename = TextureObject.RandomString(20, 40)
                            self._listView.listOfTexture.append(tex)
                            self._treegui.Refresh()
                 
                        return True
                 
                 
                class MenuCommand(c4d.plugins.CommandData):
                    dialog = None
                 
                    def Execute(self, doc):
                        if self.dialog is None:
                            self.dialog = TestDialog()
                        return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
                 
                    def RestoreLayout(self, sec_ref):
                        if self.dialog is None:
                            self.dialog = TestDialog()
                        return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
                 
                 
                def main():
                    c4d.plugins.RegisterCommandPlugin(
                        PLUGIN_ID, "Python TreeView Example", 0, None, "Python TreeView Example", MenuCommand())
                
                
                if __name__ == "__main__":
                    main()
                
                

                [URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 2
                • M
                  mogh
                  last edited by

                  Thanks ferdinand as allways you are the best.

                  I call this solved, the problems I encounter in my project are my own ... after cleaning my code - solving sorting ascending / descening will be next ... 😉

                  ferdinandF 1 Reply Last reply Reply Quote 0
                  • ferdinandF
                    ferdinand @mogh
                    last edited by ferdinand

                    Hey @mogh,

                    Thank you for the kind words and I am happy that this solved your problem.

                    While I sometimes channel here my inner librarian and press for a formal order for new topics, so that the SDK Team can answer them effectively, I want to make clear that all Cinema 4D programming questions are welcome on Plugin Café.

                    Just remember to open a new topic for new questions and to place them in the General Talk forum when they are general programming questions not directly related to our APIs. We will treat them then with a lower priority and rigor, but we usually also answer there.

                    Cheers,
                    Ferdinand

                    MAXON SDK Specialist
                    developers.maxon.net

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