Insert object in Treeview
-
The post https://developers.maxon.net/forum/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
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.
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
[URL-REMOVED] 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.
[URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.
-
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)