Hi thanks for the file, it was almost looking like mine, and here I can reproduce the issue (i didn't try with more than 4/5 files first) but if I take like 300 I agree it's slow.
The main culprit is not the drag and drops by itself (as suspected) but the insertion of the object, since each object is added one by one (since they are passed one by one into the InsertObject method). And each insertion implies a redraw of the TreeView UI. And if you look in my code sample in the main function if you generate 400 items it takes some time to draw these 400 items (like 20sec). So for each InsertObject the time goes up exponentially.
To counter that I created a temporary array, so in InsertObject I add the object to this temporary array. Then in a Timer (from the GeDialog) I check if there is new data in this temporary array. And if it's the case I add them to our root list, flush the temporary array and redraw only one time. So you still have the 20sec due to the drawing but it's way more usable than before I would say.
Finally, I also get rid of your equality operator since it blocks everything if you drag to drag and drop two time the same file (and looking at your implementation it can only return False if the other type is also a ListItem so I guess you should first try to check the type of input (str or ListItem)).
And here you are
import c4d
from c4d import gui
from c4d import plugins
from os import path
PLUGIN_ID = 1000001 # Test plugin id
class ListItem:
"""
class ListItem:
A simple class for storing a single string
Implements equality operator to be able to easily compare with Filename string BEFORE instantiation,
see InsertObject()
"""
def __init__(self, filename):
self._filename = filename # The simple String/Filename
@property
def filename(self):
return self._filename
class Tvf(gui.TreeViewFunctions):
def __init__(self):
self._draggedData = []
def GetFirst(self, root, userdata): # Bare minimum, neccessary to insert ListItems
if not root:
return None
return root[0]
def GetNext(self, root, userdata, obj): # Bare minimum, neccessary to insert ListItems
"""
Get next or None
Args:
root (list): The list
obj (ListItem): The ListItem to get the next element from the list
Returns: None or next ListItem
"""
if not root:
return None
next_idx = root.index(obj) + 1
return root[next_idx] if next_idx < len(root) else None
def AcceptDragObject(self, root, userdata, obj, dragtype, dragobject):
"""
Checks if the dragged object is allowed.
Returns NOTOK, to prevent Cinema 4Ds default action when dragobject is not accepted
"""
if dragtype == c4d.DRAGTYPE_FILENAME_OTHER:
return c4d.INSERT_AFTER, False
return c4d.NOTOK, False
def InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
"""
Creates a ListItem and initializes filename property, then adds it to the list (root)
"""
# Lock global thread so only our thread is accessing this list (so the timer can't consume data while he add things)
c4d.threading.GeThreadLock()
self._draggedData.append(ListItem(dragobject))
c4d.threading.GeThreadUnlock()
def GetName(self, root, userdata, obj):
return obj.filename
def GetId(self, root, userdata, obj):
return hash(obj)
class TestDialog(gui.GeDialog):
IDD_TREEVIEW = 10000
TV_FILENAME = 11001
def __init__(self, items):
self._items = items # Hold ref to the Filename List
self._treegui = None # the TreeViewCustomGui
self._tv = Tvf() # instantiate TreeViewFunctions
def CreateLayout(self):
self.SetTimer(250)
settings = c4d.BaseContainer()
settings.SetBool(c4d.TREEVIEW_OUTSIDE_DROP, True) # Allows dragging files from outside Cinema 4D
settings.SetBool(c4d.TREEVIEW_HAS_HEADER, True)
self._treegui = self.AddCustomGui(self.IDD_TREEVIEW, c4d.CUSTOMGUI_TREEVIEW, "",
c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,
300,
300,
settings)
return True
def InitValues(self):
layout_bc = c4d.BaseContainer()
layout_bc.SetLong(self.TV_FILENAME, c4d.LV_TREE)
self._treegui.SetLayout(1, layout_bc)
self._treegui.SetHeaderText(self.TV_FILENAME, "Filename")
self._treegui.Refresh()
self._treegui.SetRoot(self._items, self._tv, None)
return True
def Timer(self, msg):
draggedData = self._tv._draggedData
if not draggedData:
return
# Lock global thread so only our thread is accessing this list (so the TreeView can't add data while we delete things)
c4d.threading.GeThreadLock()
self._tv._draggedData = []
self._items += draggedData
c4d.threading.GeThreadUnlock()
self._treegui.Refresh()
if __name__ == '__main__':
global dlg
items = []
# Create some fake data just to demonstrate how just drawing is slow
for x in xrange(0):
items.append(ListItem(str(x)))
dlg = TestDialog(items)
dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC)
Unfortunately for the drawing speed, there is not that much that can't be done since TreeView call a lot of functions recursively (like GetFirst, GetNext) which are in our case implemented in Python. And Python is slow...
And regarding DRAGTYPE_FILES I actually see nowhere in our codebase where it's used, so I would say probably an old relic but I'm asking the development team about it and will come back if I simply overlooked something.
Cheers,
Maxime.