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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Invoke Parent Function inside / outside class (TreeView)

    General Talk
    3
    3
    612
    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 mogh

      Dear developers,

      My topic seems a bit cryptic, but I am lacking the proper chargon to be more precise.

      I have my TreeViewFunctions with a list of objects with values.
      I want a button to activate and the sum selected and the sum of values of the object calculated outside the Treeview Gui hence I want to display it outside (my thinking might not be correct route to success) , when the user check marks an element.

      I've read about the Python "super()" thing but could not implement anything remotely working in my case. I once read in the documentation about passing gui to parents but can't find its any more. Anyway I tried a lot ... I know this is a broad OOP question but hence its tied to the treeview I try my luck here anyway.

      The use-case is:
      Users selects elements -> display value on the fly -> let user interact (in my case import)

      2022-02-26-Window_000060.jpg

      My Helper -> def calc_selected(self):

      Thanks in advance.
      Mogh

      """
      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
      MY_BUTON = 5
      HOW_MANY = 6
      ID_FILESIZE = 7
      HOW_MANY_MB = 8
      MY_CALC = 9
      
      TREEVIEW = 99
      
      # 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
              self.filesize = TextureObject.RandomNumber()
      
          @staticmethod
          def RandomString(minLength, maxLength):
              """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)))
      
          @staticmethod
          def RandomNumber():
              return random.randrange(1, 99)
      
          @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.
              """
              # I only use the checkbox to select list elemenmts
              """
              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))
      
              if col == ID_FILESIZE:
                  text = obj.filesize
                  canvas = drawinfo["frame"]
                  # w = geUserArea.DrawGetTextWidth(name)
                  h = canvas.DrawGetFontHeight()
                  # xpos = drawinfo["xpos"] + 10
                  xpos = drawinfo["xpos"] + drawinfo["width"] - canvas.DrawGetTextWidth(text)
                  ypos = drawinfo["ypos"] + drawinfo["height"]
                  drawinfo["frame"].DrawText(text, 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 __init__(self):
              self.overall = len(self._listView.listOfTexture)
      
          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.
      
              if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, rows=3, cols=1, groupflags=c4d.BORDER_OUT):
                  self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                  self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE)
      
                  if self.GroupBegin(id=1001, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, rows=2, cols=3, groupflags=c4d.BORDER_OUT):
                      self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE)
                      self._treegui = self.AddCustomGui(TREEVIEW, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, minw=430, minh=160, customdata=customgui)
                      if not self._treegui:
                          print("[ERROR]: Could not create TreeView")
                          return False
                      self.GroupEnd()
      
                  if self.GroupBegin(id=1002, flags=c4d.BFH_FIT | c4d.BFV_FIT, rows=2, cols=4, groupflags=c4d.BORDER_OUT):
                      self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE)
      
                      self.AddStaticText(HOW_MANY, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_LEFT | c4d.BFH_SCALE, name="Selected: 0 / " + str(self.overall))
                      self.AddStaticText(HOW_MANY_MB, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_LEFT | c4d.BFH_SCALE, name="Filesize Sum: ____")
                      self.AddButton(MY_CALC, c4d.BFH_CENTER, name="Calc Manual")
                      self.AddButton(MY_BUTON, c4d.BFH_CENTER, name="Enable me by check-boxing")
                      self.Enable(MY_BUTON, False)
                      self.GroupEnd()
      
                  if self.GroupBegin(id=1002, flags=c4d.BFH_FIT | c4d.BFV_FIT, rows=2, cols=3, groupflags=c4d.BORDER_OUT):
                      self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                      self.GroupBorderNoTitle(c4d.BORDER_ACTIVE_3)
                      self.AddStaticText(1003, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_CENTER | c4d.BFH_SCALE, name="How do I invoke action from inside the treegui class?")
                      self.GroupEnd()
                  self.GroupEnd()
      
              return True
      
          def calc_selected(self, autoactivate):
              """
              this is a helper to calculate the selected elements and to enable the button
              I do NOT know how to trigger this everytime a user clicks on a checkbox in the treegui class
      
              :return: # slected and sum of the selecteed filsize
              """
              selected = 0
              filsizesum = 0
              for fileitem in self._listView.listOfTexture:
                  if fileitem.IsSelected is True:
                      filsizesum += fileitem.filesize
                      selected += 1
      
              if selected > 0 and autoactivate:
                  self.Enable(MY_BUTON, True)
      
              sel_string = "Selected: " + str(selected) + " / " + str(self.overall)
              self.SetString(HOW_MANY, sel_string)
      
              mb_string = "Filesize Sum: " + str(filsizesum)
              self.SetString(HOW_MANY_MB, mb_string)
      
              return selected, filsizesum
      
          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)
              layout.SetLong(ID_FILESIZE, c4d.LV_USER)
              self._layout = layout
              self._treegui.SetLayout(5, 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.SetHeaderText(ID_FILESIZE, "Filesize")
      
              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 == MY_BUTON:
                  newID = int(len(self._listView.listOfTexture) + 1)
                  tex = TextureObject()
                  tex.texturePath = "Some new data " + str(newID)
                  tex.longfilename = TextureObject.RandomString(20, 40)
                  self._listView.listOfTexture.append(tex)
                  self._treegui.Refresh()
       
              if id == MY_CALC:
                  self.calc_selected(False)
                  # print(self.calc_selected(False))
      
              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=300, defaultw=430)
       
          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.

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

        Hey @mogh,

        Thank you for reaching us. Since this is a bit abstract question, I will answer it a bit more abstractly.

        super()

        The super keyword is just a way to invoke the base implementation of a method in many languages. So, when you have this:

        class AClass:
            """Some class.
            """
            def MultiplyByTwo (self, value: int) -> int:
                """Multiplies #value by two and returns the result.
                """
                return value * 2
        
        class BClass (AClass):
            """Has AClass as its base.
            """
            def MultiplyByTwo (self, value: int, offset: int) -> int:
                """Calls the base implementation with #value and returns its result plus #offset.
                """
                r = super().MultiplyByTwo(value)
                return r + offset
        

        Then AClass.MultiplyByTwo is being shadowed by BClass.MultiplyByTwo in BClass. With super you can call that shadowed base implementation. super() in Python is a bit confusing, as its syntax has changed from Python 2 to 3 and is also dependent on the context, but its effect is quite simple:

        a = AClass()
        b = BClass()
        
        # Calls AClass.MultiplyByTwo()
        print (a.MultiplyByTwo(2))
        
        # Calls the overloaded base implementation AClass.MultiplyByTwo() with super()
        # in BClass.MultiplyByTwo(), to avoid having to write the same code twice.
        print (b.MultiplyByTwo(2, 2))
        
        4
        6
        [Finished in 139ms]
        

        In our API we sometimes recommend to call the base implementation of something, e.g. in a Message method, to ensure that all the background processing Cinema 4D is doing is carried out properly. But from what I can see, super() is not relevant in your case.

        I have my TreeViewFunctions with a list of objects with values. I want a button to activate and the sum selected and the sum of values of the object calculated outside the Treeview Gui hence I want to display it outside (my thinking might not be correct route to success) , when the user check marks an element.

        I am not 100% sure what your goals are in this case. There are, however, two simple techniques with which you should be able to solve your problem (you will only need one of them, and which one depends on your goals):

        Access an Enclosing Entity from a Subordinate Entity

        Sometimes you want to have access to a larger enclosing class/interface/data structure from something that is contained within that enclosing entity. Examples would be accessing a GeDialog from a GeUserArea hosted by it or acessing a GeDialog from a TreeViewFunctions utility interface which is attached to tree view hosted by the dialog. The answer is then to either make the enclosing entity available as a global variable (which is a bad idea) or to simply pass a reference for it to the subordinate entity. As a sketch for your case:

        class ListView(c4d.gui.TreeViewFunctions):
            """The subordinate entity.
            """ 
         
            def __init__(self, host: c4d.gui.GeDialog):
                """Initializes a ListView with a host.
        
                Args:
                    host (c4d.gui.GeDialog): The hosting dialog.
                """
                if not isinstance(host, c4d.gui.GeDialog):
                    raise TypeError(
                        f"Expected {c4d.gui.GeDialog} for argument 'host'. Received: {host}")
        
                self._host = host
        
            def DoSomethingWithDialog(self, *data):
                """Calls the dialog over the stored reference.
                """
                self._host.Open(*data)
        
        
        # ...
        
        class TestDialog(c4d.gui.GeDialog):
            """The enclosing entity.
            """
            # I am not quite sure why Maxime then did attach this stuff to the class
            # and not to the instance (which I would consider bad style).
            # In this case, apart from stylistic concerns, we cannot do this 
            # practically, as we must establish a reference between a TestDialog 
            # instance and a ListView instance.
        
            # _listView = ListView()  # Our Instance of c4d.gui.TreeViewFunctions
        
            def __init__(self):
                """
                """
                # Create an instance of ListView and give it access to the dialog
                # which carries it.
                self._listView = ListView(host=self)
        
        Extend an Interface

        The second, and likely more relevant "pattern" (and pattern is here a bit of an overstatement) is simply to extend the interface that is ListView. While TreeViewFunctions gives you an outline of what the class must be able to do (i.e., it establishes an interface), there is nothing which prevents you from extending that. Again, as a sketch:

        class ListView(c4d.gui.TreeViewFunctions):
            """The subordinate entity.
            """ 
         
            def __init__(self, dataToManage: list[SomeFancyType]):
                """Initializes a ListView with a host.
        
                Args:
                    dataToManage: The data manged by the list view.
                """
                # Dome some validation of #dataToManage ...
        
                self._data = dataToManage
        
            def CalculateSomePropertySum(self) -> int:
                """Returns the sum of #SomeProperty of all #SomeFancyType managed as
                #data by this instance.
                """
                return sum((item.SomeProperty for item in self._data))
        
        
        # ...
        
        class TestDialog(c4d.gui.GeDialog):
            """The enclosing entity.
            """
            def __init__(self):
                """
                """
                self._listView = ListView()
        
            def Command(self, cid, cmsg):
                """Invokes CalculateItemSum() on self._listView which relies on data
                which at least informally is invisible to this TestDialog instance 
                (in practice you can of course access anything in Python, but that
                often makes ugly code).
                
                Args:
                    cid (int): The command id.
                    cmsg (any): The command id message data.
                
                """
                # ...
        
                # The user wants to see a summary of SomeProperty of all items in the
                # list view.
                if cid == ID_BUTTON_SHOW_SUMMARY_SOMEPRROPERTY:
                    # Get the value by calling the method of the interface.
                    value = self._listView.CalculateSomePropertySum()
                    # Show a dialog with the result.
                    c4d.gui.MessageDialog(
                        f"The sum of 'SomeProperty' of all items is: {value}")
                    # Or set some gadget in the dialog to that value.
                    self.SetInt32(ID_SOMEPRROPERTY_SUM, int(value))
        
                # ...
        

        I hope this gives you an idea in which direction to sail with your code 🙂 I must also point out that we cannot provide full solutions here, as this is a pure design question.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

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

          🙇 👏 💃 😌 Thank You @ferdinand

          Access an Enclosing Entity from a Subordinate Entity, was the solution I was searching for hence I needed to alter the GeDialog from inside the TreeViewFunctions. This seems to be the only solution hence these checkboxes belong to the treeview ?!?

          Anyway its working and I am very happy 🙂

          User sets checkbox -> Sum is calculated -> button is activated.

          I updated the example code (and removed the now unnecessary button to prevent further confusion) with the desired behavior for future readers.

          kind regards
          mogh

          """
          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
          MY_BUTON = 5
          HOW_MANY = 6
          ID_FILESIZE = 7
          HOW_MANY_MB = 8
          MY_CALC = 9
          
          TREEVIEW = 99
          
          # 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
                  self.filesize = TextureObject.RandomNumber()
          
              @staticmethod
              def RandomString(minLength, maxLength):
                  """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)))
          
              @staticmethod
              def RandomNumber():
                  return random.randrange(1, 99)
          
              @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, host):
                  """Initializes a ListView with a host.
          
                  Args:
                      host (c4d.gui.GeDialog): The hosting dialog.
                  """
                  # Add ten mock data texture objects.
                  self.listOfTexture = [TextureObject() for _ in range(10)]
                  if not isinstance(host, c4d.gui.GeDialog):
                      raise TypeError("Expected {} for argument 'host'. Received: {}".format(c4d.gui.GeDialog, host))
                  else:
                      self._host = host
          
              def DoSomethingWithDialog(self):
                  """Calls the dialog over the stored reference.
                  """
                  self._host.calc_selected()
          
              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.
                  """
                  # I only use the checkbox to select list elemenmts
                  """
                  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()
          
                  self.DoSomethingWithDialog()
          
           
              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))
          
                  if col == ID_FILESIZE:
                      text = obj.filesize
                      canvas = drawinfo["frame"]
                      # w = geUserArea.DrawGetTextWidth(name)
                      h = canvas.DrawGetFontHeight()
                      # xpos = drawinfo["xpos"] + 10
                      xpos = drawinfo["xpos"] + drawinfo["width"] - canvas.DrawGetTextWidth(text)
                      ypos = drawinfo["ypos"] + drawinfo["height"]
                      drawinfo["frame"].DrawText(text, 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 __init__(self):
                  """
                  """
                  # Create an instance of ListView and give it access to the dialog
                  # which carries it.
                  self._listView = ListView(host=self)
                  self.overall = len(self._listView.listOfTexture)
          
              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.
          
                  if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, rows=2, cols=1, groupflags=c4d.BORDER_OUT):
                      self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                      self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE)
          
                      if self.GroupBegin(id=1001, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, rows=2, cols=3, groupflags=c4d.BORDER_OUT):
                          self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE)
                          self._treegui = self.AddCustomGui(TREEVIEW, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, minw=430, minh=160, customdata=customgui)
                          if not self._treegui:
                              print("[ERROR]: Could not create TreeView")
                              return False
                          self.GroupEnd()
          
                      if self.GroupBegin(id=1002, flags=c4d.BFH_FIT | c4d.BFV_FIT, rows=2, cols=3, groupflags=c4d.BORDER_OUT):
                          self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE)
          
                          self.AddStaticText(HOW_MANY, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_LEFT | c4d.BFH_SCALE, name="Selected: 0 / " + str(self.overall))
                          self.AddStaticText(HOW_MANY_MB, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_LEFT | c4d.BFH_SCALE, name="Filesize Sum: __________")
                          self.AddButton(MY_BUTON, c4d.BFH_CENTER, name="Enable me by check-boxing")
                          self.Enable(MY_BUTON, False)
                          self.GroupEnd()
          
                      self.GroupEnd()
          
                  return True
          
              def calc_selected(self):
                  """this is a helper to calculate the selected elements and to enable the button
          
                  :return: # slected and sum of the selecteed filsize
                  """
                  selected = 0
                  filsizesum = 0
                  self.overall = len(self._listView.listOfTexture)  # update our count
          
                  for fileitem in self._listView.listOfTexture:
                      if fileitem.IsSelected is True:
                          filsizesum += fileitem.filesize
                          selected += 1
          
                  if selected > 0:
                      self.Enable(MY_BUTON, True)
                  else:
                      self.Enable(MY_BUTON, False)
          
                  sel_string = "Selected: " + str(selected) + " / " + str(self.overall)
                  self.SetString(HOW_MANY, sel_string)
          
                  mb_string = "Filesize Sum: " + str(filsizesum)
                  self.SetString(HOW_MANY_MB, mb_string)
          
                  return selected, filsizesum
          
              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)
                  layout.SetLong(ID_FILESIZE, c4d.LV_USER)
                  self._layout = layout
                  self._treegui.SetLayout(5, 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.SetHeaderText(ID_FILESIZE, "Filesize")
          
                  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 == MY_BUTON:
                      newID = int(len(self._listView.listOfTexture) + 1)
                      tex = TextureObject()
                      tex.texturePath = "Some new data " + str(newID)
                      tex.longfilename = TextureObject.RandomString(20, 40)
                      self._listView.listOfTexture.append(tex)
                      self.calc_selected()
                      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=300, defaultw=430)
           
              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.

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