How to Override DoubleClick() from the TreeViewFunctions?

  • 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:

    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?


  • 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


  • @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.

  • 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.


  • 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
       obj._data.lastName = str


  • Thanks for the response

    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


    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"

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

    RE: If I omit the Return True

    the documentation say :


    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
    import c4d
    # Be sure to use a unique ID obtained from
    PLUGIN_ID = 1000010 # TEST ID ONLY
    # TreeView Column IDs.
    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
        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:
            elif mode == c4d.SELECTION_ADD:
            elif mode == c4d.SELECTION_SUB:
        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:
        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
                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.
              (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
            c4d.gui.MessageDialog("You clicked on " + str(obj))
            # if the col 2 is double clicked
            if (col == 2):
                obj.otherData = "default"
                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:
    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")
            # 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))
                # Refresh the TreeView
            return True
    def main():
        global dlg
        dlg = TestDialog()
        dlg.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
    if __name__ == "__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 = "my new name"


  • This post is deleted!

  • @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).


Log in to reply