This is a bit messy at the moment, but can be a very useful and powerful control.

For now, just try running the code. Try the following:

Todo:

How it works:

- Eric

   1 #used some code from the WxPython wiki:
   2 #-1.4 Recursively building a list into a wxTreeCtrl (yet another sample) by Rob
   3 #-1.5 Simple Drag and Drop by Titus
   4 #-TraversingwxTree
   5 
   6 #tested on wxPython 2.5.4 and Python 2.4, under Windows and Linux
   7 
   8 import  wx
   9 
  10 #---------------------------------------------------------------------------
  11 
  12 class MyTreeCtrl(wx.TreeCtrl):
  13     def __init__(self, parent, id, pos, size, style, log):
  14         wx.TreeCtrl.__init__(self, parent, id, pos, size, style)
  15         self.log = log
  16 
  17     def Traverse(self, func, startNode): 
  18         """Apply 'func' to each node in a branch, beginning with 'startNode'. """
  19         def TraverseAux(node, depth, func): 
  20             nc = self.GetChildrenCount(node, 0) 
  21             child, cookie = self.GetFirstChild(node)
  22             # In wxPython 2.5.4, GetFirstChild only takes 1 argument
  23             for i in xrange(nc): 
  24                 func(child, depth)                
  25                 TraverseAux(child, depth + 1, func)
  26                 child, cookie = self.GetNextChild(node, cookie)
  27         func(startNode, 0) 
  28         TraverseAux(startNode, 1, func) 
  29 
  30     def ItemIsChildOf(self, item1, item2):
  31         ''' Tests if item1 is a child of item2, using the Traverse function '''
  32         self.result = False
  33         def test_func(node, depth):
  34             if node == item1:
  35                 self.result = True
  36                 
  37         self.Traverse(test_func, item2)
  38         return self.result
  39 
  40     def SaveItemsToList(self, startnode):
  41         ''' Generates a python object representation of the tree (or a branch of it),
  42             composed of a list of dictionaries with the following key/values:
  43             label:      the text that the tree item had
  44             data:       the node's data, returned from GetItemPyData(node)
  45             children:   a list containing the node's children (one of these dictionaries for each)
  46         '''
  47         global list
  48         list = []
  49         
  50         def save_func(node, depth):
  51             tmplist = list
  52             for x in range(depth):
  53                 if type(tmplist[-1]) is not dict:
  54                     tmplist.append({})
  55                 tmplist = tmplist[-1].setdefault('children', [])
  56 
  57             item = {}
  58             item['label'] = self.GetItemText(node)
  59             item['data'] = self.GetItemPyData(node)
  60             item['icon-normal'] = self.GetItemImage(node, wx.TreeItemIcon_Normal)
  61             item['icon-selected'] = self.GetItemImage(node, wx.TreeItemIcon_Selected)
  62             item['icon-expanded'] = self.GetItemImage(node, wx.TreeItemIcon_Expanded)
  63             item['icon-selectedexpanded'] = self.GetItemImage(node, wx.TreeItemIcon_SelectedExpanded)
  64            
  65             tmplist.append(item)
  66             
  67         self.Traverse(save_func, startnode)
  68         return list
  69         
  70     def InsertItemsFromList(self, itemlist, parent, insertafter=None, appendafter=False):
  71         ''' Takes a list, 'itemslist', generated by SaveItemsToList, and inserts
  72             it in to the tree. The items are inserted as children of the
  73             treeitem given by 'parent', and if 'insertafter' is specified, they
  74             are inserted directly after that treeitem. Otherwise, they are put at
  75             the beginning.
  76             
  77             If 'appendafter' is True, each item is appended. Otherwise it is prepended.
  78             In the case of children, you want to append them to keep them in the same order.
  79             However, to put an item at the start of a branch that has children, you need to
  80             use prepend. (This will need modification for multiple inserts. Probably reverse
  81             the list.)
  82 
  83             Returns a list of the newly inserted treeitems, so they can be
  84             selected, etc..'''
  85         newitems = []
  86         for item in itemlist:
  87             if insertafter:
  88                 node = self.InsertItem(parent, insertafter, item['label'])
  89             elif appendafter:
  90                 node = self.AppendItem(parent, item['label'])
  91             else:
  92                 node = self.PrependItem(parent, item['label'])
  93             self.SetItemPyData(node, item['data'])
  94             self.SetItemImage(node, item['icon-normal'], wx.TreeItemIcon_Normal)
  95             self.SetItemImage(node, item['icon-selected'], wx.TreeItemIcon_Selected)
  96             self.SetItemImage(node, item['icon-expanded'], wx.TreeItemIcon_Expanded)
  97             self.SetItemImage(node, item['icon-selectedexpanded'], wx.TreeItemIcon_SelectedExpanded)
  98 
  99             newitems.append(node)
 100             if 'children' in item:
 101                 self.InsertItemsFromList(item['children'], node, appendafter=True)
 102         return newitems
 103     
 104 def OnCompareItems(self, item1, item2):
 105         t1 = self.GetItemText(item1)
 106         t2 = self.GetItemText(item2)
 107         self.log.WriteText('compare: ' + t1 + ' <> ' + t2 + '\n')
 108         if t1 < t2: return -1
 109         if t1 == t2: return 0
 110         return 1
 111 
 112 
 113 #---------------------------------------------------------------------------
 114 
 115 class TestTreeCtrlPanel(wx.Panel):
 116     def __init__(self, parent, log):
 117         # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
 118         wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
 119         self.Bind(wx.EVT_SIZE, self.OnSize)
 120 
 121         self.log = log
 122         tID = wx.NewId()
 123 
 124         self.tree = MyTreeCtrl(self, tID, wx.DefaultPosition, wx.DefaultSize,
 125                                     wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS, self.log)
 126         # Example needs some more work to use wx.TR_MULTIPLE
 127 
 128         isize = (16,16)
 129         il = wx.ImageList(isize[0], isize[1])
 130         fldridx   = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER,  wx.ART_OTHER, isize))
 131         fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER,isize))
 132         fileidx   = il.Add(wx.ArtProvider_GetBitmap(wx.ART_REPORT_VIEW, wx.ART_OTHER,isize))
 133 
 134         self.tree.SetImageList(il)
 135         self.il = il
 136 
 137         self.root = self.tree.AddRoot("The Root Item")
 138         self.tree.SetPyData(self.root, {"type":"container"})
 139         self.tree.SetItemImage(self.root, fldridx, wx.TreeItemIcon_Normal)
 140         self.tree.SetItemImage(self.root, fldropenidx, wx.TreeItemIcon_Expanded)
 141 
 142         for x in range(15):
 143             child = self.tree.AppendItem(self.root, "Item %d" % x)
 144             self.tree.SetPyData(child, {"type":"container"})
 145             self.tree.SetItemImage(child, fldridx, wx.TreeItemIcon_Normal)
 146             self.tree.SetItemImage(child, fldropenidx, wx.TreeItemIcon_Expanded)
 147             for y in range(5):
 148                 last = self.tree.AppendItem(child, "item %d-%s" % (x,chr(ord("a")+y)))
 149                 self.tree.SetPyData(last,{"type":"container"})
 150                 self.tree.SetItemImage(last, fldridx, wx.TreeItemIcon_Normal)
 151                 self.tree.SetItemImage(last, fldropenidx,wx.TreeItemIcon_Expanded)
 152                 for z in range(5):
 153                     item = self.tree.AppendItem(last,  "item %d-%s-%d" % (x, chr(ord("a")+y), z))
 154                     self.tree.SetPyData(item, {"type":"item"})
 155                     self.tree.SetItemImage(item, fileidx, wx.TreeItemIcon_Normal)
 156                     self.tree.SetItemImage(item, fileidx, wx.TreeItemIcon_Selected)
 157 
 158         self.tree.Expand(self.root)
 159         self.tree.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
 160         self.tree.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
 161         self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
 162 
 163         # These go at the end of __init__
 164         self.tree.Bind(wx.EVT_TREE_BEGIN_RDRAG, self.OnBeginRightDrag)
 165         self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnBeginLeftDrag)
 166         self.tree.Bind(wx.EVT_TREE_END_DRAG, self.OnEndDrag)
 167 
 168     def OnBeginLeftDrag(self, event):
 169         '''Allow drag-and-drop for leaf nodes.'''
 170         self.log.WriteText("OnBeginDrag")
 171         event.Allow()
 172         self.dragType = "left button"
 173         self.dragItem = event.GetItem()
 174 
 175     def OnBeginRightDrag(self, event):
 176         '''Allow drag-and-drop for leaf nodes.'''
 177         self.log.WriteText("OnBeginDrag")
 178         event.Allow()
 179         self.dragType = "right button"
 180         self.dragItem = event.GetItem()
 181 
 182     def OnEndDrag(self, event):
 183         print "OnEndDrag"
 184         
 185         # If we dropped somewhere that isn't on top of an item, ignore the event
 186         if event.GetItem().IsOk():
 187             target = event.GetItem()
 188         else:
 189             return
 190 
 191         # Make sure this member exists.
 192         try:
 193             source = self.dragItem
 194         except:
 195             return
 196 
 197         # Prevent the user from dropping an item inside of itself
 198         if self.tree.ItemIsChildOf(target, source):
 199             print "the tree item can not be moved in to itself! "
 200             self.tree.Unselect()
 201             return
 202         
 203         # Get the target's parent's ID
 204         targetparent = self.tree.GetItemParent(target)
 205         if not targetparent.IsOk():
 206             targetparent = self.tree.GetRootItem()
 207        
 208         # One of the following methods of inserting will be called...   
 209         def MoveHere(event):
 210             # Save + delete the source
 211             save = self.tree.SaveItemsToList(source)
 212             self.tree.Delete(source)
 213             newitems = self.tree.InsertItemsFromList(save, targetparent, target)
 214             #self.tree.UnselectAll()
 215             for item in newitems:
 216                 self.tree.SelectItem(item)
 217             
 218         def InsertInToThisGroup(event):
 219             # Save + delete the source
 220             save = self.tree.SaveItemsToList(source)
 221             self.tree.Delete(source)
 222             newitems = self.tree.InsertItemsFromList(save, target)
 223             #self.tree.UnselectAll()
 224             for item in newitems:
 225                 self.tree.SelectItem(item)
 226         #---------------------------------------
 227             
 228         if self.tree.GetPyData(target)["type"] == "container" and self.dragType == "right button":
 229             menu = wx.Menu()
 230             menu.Append(101, "Move to after this group", "")
 231             menu.Append(102, "Insert into this group", "")
 232             menu.UpdateUI()
 233             menu.Bind(wx.EVT_MENU, MoveHere, id=101)
 234             menu.Bind(wx.EVT_MENU, InsertInToThisGroup,id=102)
 235             self.PopupMenu(menu)
 236         else:
 237             if self.tree.IsExpanded(target):
 238                InsertInToThisGroup(None)
 239             else:
 240                MoveHere(None)
 241 
 242     def OnRightUp(self, event):
 243         pt = event.GetPosition();
 244         item, flags = self.tree.HitTest(pt)
 245         self.log.WriteText("OnRightUp: %s (manually starting label edit)\n" % self.tree.GetItemText(item))
 246         self.tree.EditLabel(item)
 247 
 248     def OnLeftDown(self, event):
 249         print "control key is", event.m_controlDown
 250 
 251         pt = event.GetPosition();
 252         item, flags = self.tree.HitTest(pt)
 253         self.tree.SelectItem(item)
 254         event.Skip()
 255 
 256     def OnRightDown(self, event):
 257         print "control key is", event.m_controlDown
 258         
 259         pt = event.GetPosition();
 260         item, flags = self.tree.HitTest(pt)
 261         self.tree.SelectItem(item)
 262         event.Skip()
 263 
 264     def OnLeftDClick(self, event):
 265         pt = event.GetPosition();
 266         item, flags = self.tree.HitTest(pt)
 267         self.log.WriteText("OnLeftDClick: %s\n" % self.tree.GetItemText(item))
 268 
 269         #expand/collapse toggle
 270         self.tree.Toggle(item)
 271         print "toggled ", item
 272         #event.Skip()
 273 
 274     def OnSize(self, event):
 275         w,h = self.GetClientSizeTuple()
 276         self.tree.SetDimensions(0, 0, w, h)
 277 
 278 
 279 #---------------------------------------------------------------------------
 280 
 281 class MyLog:
 282     def __init__(self):
 283         pass                    
 284     def WriteText(self, text):
 285         print text
 286 
 287 class MyFrame(wx.Frame):
 288     def __init__(self, *args, **kwds):
 289         wx.Frame.__init__(self, *args, **kwds)
 290         log = MyLog()
 291         pnl = TestTreeCtrlPanel(self, log)
 292                   
 293 class MyApp(wx.App):
 294     def OnInit(self):
 295         wx.InitAllImageHandlers()
 296         frame_1 = MyFrame(None, -1, "")
 297         self.SetTopWindow(frame_1)
 298         frame_1.Show(1)
 299         return 1
 300 
 301 # end of class MyApp
 302 
 303 if __name__ == "__main__":
 304     app = MyApp(0)
 305     app.MainLoop()

Comment by Franz Steinhäusler

Cool control and sample!

DragAndDropWithFolderMovingAndRearranging (last edited 2011-05-24 04:51:27 by 208)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.