Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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

    How to Override DoubleClick() from the TreeViewFunctions?

    Cinema 4D SDK
    r21 python
    3
    9
    1.0k
    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.
    • B
      bentraje
      last edited by

      Hi,

      By default, when you double click in a TreeView item, an editable text box appears and allows you to rename the TreeView item. Using the code from @Donovan Keith, it results to the following:
      https://www.dropbox.com/s/oxbr1o68vwjfwd3/c4d268_override_double_click_treeview.mp4?dl=0

      It works if you are using the c4d objects but not if you are using custom nodes.
      I don't know how to override the DoubleClick. The following code prints the statement properly but it doesn't call the "renaming" feature or fetch the new string.

          def DoubleClick(self, root, userdata, obj, col, mouseinfo):
      
              print "Please Rename Me"
              return True
      

      The result can be seen here.

      Is there a way around this?

      Regards,
      Ben

      1 Reply Last reply Reply Quote 0
      • ManuelM
        Manuel
        last edited by

        hi,

        Your DoubleClick function is returning True, so the renaming function is not kicking in.

        You could open a "rename" dialogbox and do the rename in the DoubleClick function or set a "global" variable where you can store the col and let the GetName and SetName

        Cheers,
        Manuel

        MAXON SDK Specialist

        MAXON Registered Developer

        1 Reply Last reply Reply Quote 0
        • B
          bentraje
          last edited by

          @m_magalhaes

          Thanks for the response.

          RE: You could open a "rename" dialogbox
          I understand I can do that, but I prefer the default behavior of the double click: An edit text appears and when I hit enter the treeview item is immediately renamed.

          Is this still possible with non native C4D objects?

          I guess in more direct term what is the UI code for
          when I double click an edit text appears and when I hit enter the treeview item is immediately renamed.

          See video above in the first post for reference.

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

            Hm,

            probably not what you are looking for, but when you run out of other options, you could try to use mouseinfo and the screen position of your hosting dialog to open a borderless dialog just in the right place, i.e. over the element you want to rename. Will probably require some fiddling, but should be possible.

            Cheers,
            zipit

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 1
            • ManuelM
              Manuel
              last edited by Manuel

              hi,

              Sorry my answer wasn't clear. I'm not using c4d object.

              The rename default behaviour will not happen if your double click function return True. (meaning the double click have been handled)

              It's not related with using default c4d objects or not.

              So don't return true in your DoubleClick function.

              The "problem" is that it will use the GetName/SetName functions. Those functions doesn't know what columns have been clicked.
              When you double click, the GetName function is called to display the text that you want to rename, and when you press enter, the SetName is called.

              Imagine you have FirstName and LastName. You double click the FirstName. In the DoubleClick function you will set a variable "FirstName"
              In the SetName function you will be able to use this kind of "code"

              # just to get the idea
              if self.firstName:
                 obj._data.firstName = str
              else:
                 obj._data.lastName = str
              

              Cheers
              Manuel.

              MAXON SDK Specialist

              MAXON Registered Developer

              1 Reply Last reply Reply Quote 1
              • B
                bentraje
                last edited by

                Thanks for the response

                @zipit
                Yea I think that would be the last resort as it might end up with more code than just having ah popup rename dialog. I was just thinking of leveraging the default behavior and fetching the old name and the new name

                @m_magalhaes

                RE: If I omit the Return True
                It will give me an error of TypeError: DoubleClick expected bool, not None

                RE: GetName function is called to display the text
                RE: when you press enter, the SetName is called.
                Correct me if I'm wrong the functions GetName and SetName are only available only on default C4D objects. I can't use it.

                RE: Imagine you have FirstName and LastName. You double click the FirstName
                Sorry I don't follow this. In my video example above, I don't have any other column, except 1.

                Apologies if this is longer than expected, but it is there I can fetch the new name without using the GetName as it does not work on my obj since its not a native C4D.
                For instance,

                1. There is a name variable
                2. Current value is "Folder A". Corresponds with the name of the Tree Item.
                3. Double Click. In place add edit text appears. Then Rename it to "Folder B".
                4. Store the new string to name variable
                5. Print name variable. Should be "Folder B"
                1 Reply Last reply Reply Quote 0
                • ManuelM
                  Manuel
                  last edited by Manuel

                  @bentraje said in How to Override DoubleClick() from the TreeViewFunctions?:

                  RE: If I omit the Return True

                  the documentation say :

                  Returns:

                  True if the event was handled, otherwise False.

                  So you double click, it call the double click function. If you return True, cinema4D will think you have handle that double click and will not call the default "rename" function.
                  If you return False, Cinema4D will think that you couldn't handle it and will launch it's default function to rename the object.

                  You have to differentiate the data and the UI. It's two thing different with different purpose and functions.

                  The TreeViewFunction have GetName and SetName functions. This tree purpose is to display hierarchies. That's why it also have those function, InsertUnder, GetNext etc etc.
                  Those function are here to make the UI understand how to retrieve and handle your data.

                  When you are using the c4d's object, they come with all those function by default. But that doesn't mean you can't use your own data with there own function.
                  It's easier to use c4d's object but not mandatory.

                  I've picked maxime's example and adapt it so you can understand.

                  It has two columns. Depending on where you click, it will either set a data do "default" or return false and c4d will call the SetName of the TreeViewFunction were you can change your data.

                  I didn't implemented SetName on my object were i store my data.

                  I also passed a link of the GUI to the TreeViewFunction so i can refresh it inside the DoubleClickFunction

                  #More information https://developers.maxon.net/forum/topic/10654#56287
                  import c4d
                  
                  # 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
                  
                  class TextureObject(object):
                      """
                      Class which represent a texture, aka an Item in our list
                      """
                      texturePath = "TexPath"
                      otherData = "OtherData"
                      _selected = False
                  
                      def __init__(self, texturePath):
                          self.texturePath = texturePath
                          self.otherData += texturePath
                  
                      @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, dlgPointer):
                          self.listOfTexture = list() # Store all objects we need to display in this list
                          self.dlgPointer = dlgPointer
                          # Add some defaults values 
                          t1 = TextureObject("T1")
                          t2 = TextureObject("T2")
                          t3 = TextureObject("T3")
                          t4 = TextureObject("T4")
                  
                          self.listOfTexture.extend([t1, t2, t3, t4])
                  
                  
                      def IsResizeColAllowed(self, root, userdata, lColID):
                          return True
                  
                      def IsTristate(self, root, userdata):
                          return False
                  
                      def GetColumnWidth(self, root, userdata, obj, col, area):
                          return 80  # All have the same initial 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 SetName(self, root, userdata, obj, str):
                          obj.otherData = str
                  
                  
                      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
                          """
                          rgbSelectedColor = c4d.gui.GeUserArea().GetColorRGB(c4d.COLOR_TEXT_SELECTED)
                          selectedColor = c4d.Vector(rgbSelectedColor["r"], rgbSelectedColor["g"], rgbSelectedColor["b"]) / 255.0
                          txtColor = selectedColor if obj.IsSelected else c4d.Vector(0.2, 0.4, 0.8)
                          drawinfo["frame"].DrawSetTextCol(txtColor, drawinfo["bgCol"])
                  
                          if col == ID_NAME:
                              name = str(obj)
                              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)
                              xpos = drawinfo["xpos"]
                              ypos = drawinfo["ypos"] + drawinfo["height"]
                  
                          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))
                          # if the col 2 is double clicked
                          if (col == 2):
                              obj.otherData = "default"
                              self.dlgPointer._treegui.Refresh()
                              return True
                          return False
                  
                      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
                      
                      def __init__(self):
                          self._listView = ListView(self) # 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_USER)
                          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
                  
                  
                  def main():
                      global dlg
                      dlg = TestDialog()
                      dlg.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
                  
                  if __name__ == "__main__":
                      main()
                  

                  let me know if something is not clear. I'm trying to be more generic than just answer your question 🙂

                  in your problem just do something like

                  obj.name = "my new name"
                  

                  Cheers,
                  Manuel


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

                  MAXON SDK Specialist

                  MAXON Registered Developer

                  1 Reply Last reply Reply Quote 1
                  • B
                    bentraje
                    last edited by

                    This post is deleted!
                    1 Reply Last reply Reply Quote 0
                    • B
                      bentraje
                      last edited by

                      @m_magalhaes

                      Thanks for the response.
                      The confusion is entirely mine.

                      I was confused because I initially thought the GetName and SetName is only for typical C4D objects.
                      but there is actually a separate GetName and SetName functions for the TreeView objects.

                      I was able to retrieve the new string by just this code:

                          def SetName(self,root, userdata, obj, name):
                      
                              print name # new name when you hit enter
                      

                      I can now use the name variable to use in my separate renaming function (i.e. rename a folder for which the TreeView was based on).

                      Thanks!

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