Insert object in Treeview



  • The post https://plugincafe.maxon.net/topic/10654/14102_using-customgui-listview gives a great example of using Treeview.
    I tried to add an InsertUnder, but that does not work.
    What am I doing wrong?
    I tested it using R20 and R21.

    # In CreateLayout() I added a simple button
            self.AddButton(1002, c4d.BFH_CENTER, name="Insert Under First")
    
    # In Command() I added:
            if id == 1002:
                # Insert Under
                tex = TextureObject("Inserted under first item.")
                #self._listView.SetDragObject(root, userdata, tex)
    
                first = self._listView.GetFirst(self._treegui, self._listView)
                print "First: ", first                # seems ok. It returns the first object T1
    
                #InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
                self._listView.InsertObject(self._treegui, self._listView, first, c4d.DRAGTYPE_FILENAME_OTHER, tex, c4d.INSERT_UNDER, True)
                
                # Refresh the TreeView
                self._treegui.Refresh()
    
    # In ListView() I added:
        def InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
            print "Insert Object obj: ", obj                       #seems ok, T1
            print "dragobject: ", dragobject                    #seems ok, Inserted under first item. 
            #self._listView.InsertObject(self._treegui, self._listView, first, c4d.DRAGTYPE_FILENAME_OTHER, tex, c4d.INSERT_UNDER, True)
            return True
    


  • Ok, after some more reading I now understand that I should insert the object in the listview myself.

        def InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
            self.listOfTexture.append(dragobject)
            return True
    

    03cbb8df-61c9-4f0c-9979-c2b2b0412f0d-image.png
    But now it is inserted at the end (makes sense, because I use an append()).
    My question is how to insert the new object under another one?

    Something like this.
    037ef554-259c-451c-9e5b-6fdaa71e4256-image.png

    I am trying to make treeview of a folder with its subfolders and files.



  • Hi @pim

    Basically as explained in TreeView made simple – Part 1 you have to override GetDown to make it actually return the first child.
    Then GetNext will be called to retrieve the next children.

    So I would say it's more about how to structure your data that matters.
    But since you seem to use the previous example I extended it to also support children.

    So first we will extend our TextureObject to support children.
    To do so we will add a list that will store all children and also a weakref of our parent. (don't forget to import weakref).
    If you don't know what is a weakref I encourage you to read weakref – Garbage-collectable references to objects.

        def __init__(self, texturePath):
            self.texturePath = texturePath
            self.otherData += texturePath
            self.children = [] # This will store all children of the current TextureObject.
            self._parent = None # This will store a weakreaf (so don't forget to import weakref) to the parent.
    

    Then we will define some convenient functions in our TextureObject:

        def AddChild(self, obj):
            obj._parent = weakref.ref(self)
            self.children.append(obj)
            
        def GetChildren(self):
            return self.children
    
        def GetParent(self):
            if self._parent:
                return self._parent()
            
            return None
    

    Once it's done it's time to adapt our TreeViewFunctions implementation to add support for children.
    So the first thing to override is GetDown() to retrieve the first child of a given TextureObject like so.

        def GetDown(self, root, userdata, obj):
            """
            Return a child of a node, since we only want a list, we return None everytime
            """
            children = obj.GetChildren()
            if children:
                return children[0]
    
            return None
    

    The treeview will call GetDown to retrieve the first child, then to retrieve the next child it will call GetNext on it.
    So we need to adapt GetNext and GetPref to also support children.
    Typically we will use the same logic, the only difference is is the current obj have a parent, look for self in the parent list instead of the global one stored in our TreeViewFunctions implementation.

        def GetNext(self, root, userdata, obj):
            """
            Returns the next Object to display after arg:'obj'
            """
            rValue = None
    
            # If does have a child it means it's a child.
            objParent = obj.GetParent()
            listToSearch = objParent.GetChildren() if objParent is not None else self.listOfTexture
    
            currentObjIndex = listToSearch.index(obj)
            nextIndex = currentObjIndex + 1
            if nextIndex < len(listToSearch):
                rValue = listToSearch[nextIndex]
    
            return rValue
    
        def GetPred(self, root, userdata, obj):
            """
            Returns the previous Object to display before arg:'obj'
            """
            rValue = None
    
            # If does have a child it means it's a child.
            objParent = obj.GetParent()
            listToSearch = objParent.GetChildren() if objParent is not None else self.listOfTexture
    
            currentObjIndex = listToSearch.index(obj)
            predIndex = currentObjIndex - 1
            if 0 <= predIndex < len(listToSearch):
                rValue = listToSearch[predIndex]
    
            return rValue
    

    We have everything needed now to display everything.

    However, we also need to adapt DeletePressed to support children.
    DeletePressed is a global event without a passed obj.
    Previously we iterated listOfTexture so we need to support children. To do so let's make an iterator that will iterate all nodes and all children.
    And let use it in the DeletePressed function.

    # This is a global function which accept a list and will iterate over each TextureObject
    # of the passed list to retrieve all TextureObject and all their children.
    def TextureObjectIterator(lst):
        
        for parentTex in lst:
            yield parentTex
            TextureObjectIterator(parentTex.GetChildren())
    
    
        def DeletePressed(self, root, userdata):
            "Called when a delete event is received."
    		
    		# Convert the iterator to a list to be able to reverse it
            for tex in reversed(list(TextureObjectIterator(self.listOfTexture))):
                if tex.IsSelected:
                    objParent = tex.GetParent()
                    listToRemove = objParent.GetChildren() if objParent is not None else self.listOfTexture
                    listToRemove.remove(tex)
    

    Now everything is done for the TreeViewFunctions implementation.
    So let's add a new button in our GeDialog CreateLayout to add a child to the selected TextureObject.
    And defines its behavior when clicked in the GeDialog Command.

    		# In CreateLayout
            self.AddButton(1002, c4d.BFH_CENTER, name="Add Child to selected")
    		
    		# In Command
            if id == 1002:
                for parentTex in TextureObjectIterator(self._listView.listOfTexture):
                    if not parentTex.IsSelected:
                        continue
    
                    newID = len(parentTex.GetChildren()) + 1 
                    tex = TextureObject("T{0}.{1}".format(str(parentTex), newID))
                    parentTex.AddChild(tex)
                    
                # Refresh the TreeView
                self._treegui.Refresh()
    

    Now that we have everything we may also want to support the folding state to show/hide children in the treeview.
    So let's enhance our TextureObject to support folding (very similar to selection state).

    class TextureObject(object):
    
        _open = True
    	
        @property
        def IsOpened(self):
            return self._open
    
        def Open(self):
            self._open = True
        
        def Close(self):
            self._open = False
    

    Then in the TreeViewFunctions implementation we need to override IsOpened and Open.

        def IsOpened(self, root, userdata, obj):
            """
            Returns: (bool): Status If it's opened = True (folded) or closed = False.
            """
            return obj.IsOpened()
    
        def Open(self, root, userdata, obj, onoff):
            """
            Called when the user clicks on a folding state of an object to display/hide its children
            """
            if onoff:
                obj.Open()
    
            else:
                obj.Close()
    

    And here you are, find the full code in pastebin, keep in mind it's only one way to store data.
    How you decide to store data is up to you.

    In this case, it would make more sense to have a TextureObject as root, so this way all TextureObject will be handled in the same way and the first level is not stored in a specific one level the only list.
    Maybe it will be for part 3 of TreeView Exploration! :)

    Cheers,
    Maxime.



  • Thanks, great explanation!
    One small issue. Delete doesn't work because objParent' is not defined.

    Traceback (most recent call last):
    File "scriptmanager", line 251, in DeletePressed
    NameError: global name 'objParent' is not defined
    

    Here the code that, I think, solves the issue:

        def DeletePressed(self, root, userdata):
            "Called when a delete event is received."
            for tex in reversed(list(TextureObjectIterator(self.listOfTexture))):
                if tex.IsSelected:
                    objParent = tex.GetParent()               # Added
                    listToRemove = objParent.GetChildren() if objParent is not None else self.listOfTexture
                    listToRemove.remove(tex)
    

Log in to reply