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:
- dragging folders around
- dragging something in to a closed folder vs. an open folder
- dragging something on to a folder with the right mouse button
- doing something illegal; like moving something inside itself (this is caught and blocked)
Todo:
- clean up code
- add support for multiple selection / moving multiple items (should be easy)
How it works:
- First, the wxTreeCtrl demo was made in to a standalone demo
the TreeControls "Simple Drag and Drop" code from this wiki was added
to enable moving folders, it needed code to save a portion of the tree in to a single python object. I did this with a modified version of TraversingwxTree
- 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!