Contents
-
Tree Controls
- Lazy Evaluation Tree Controls
- Lazy Evaluation Directory Browser Example
- Recursively building a directory list into a wxTreeCtrl
- Building a tree non-recursively
- Drag and Drop with a wxTreeCtrl
- Recursively building a list into a wxTreeCtrl (yet another sample)
- Simple Drag and Drop
- Drag and Drop with Folder Moving and Rearranging
- TreeCtrl with automatic scrolling while dragging
Tree Controls
wxTreeCtrl Lazy evaluation (using expansion/scrolling events to load only the visible elements into the controls) and other stuff(look through the page)
See also:
Lazy Evaluation Tree Controls
The lazy tree demonstrates basic Lazy Evaluation operation for a wxTreeCtrl. The demo code is a little contrived as it's intended to show what's required to do lazy evaluation rather than show a real-world implementation.
1 import wx, random
2
3
4 class LazyTree(wx.TreeCtrl):
5 ''' LazyTree is a simple "Lazy Evaluation" tree, that is, it only adds
6 items to the tree view when they are needed.'''
7
8 def __init__(self, *args, **kwargs):
9 super(LazyTree, self).__init__(*args, **kwargs)
10 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnExpandItem)
11 self.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self.OnCollapseItem)
12 self.__collapsing = False
13 root = self.AddRoot('root')
14 self.SetItemHasChildren(root)
15
16 def OnExpandItem(self, event):
17 # Add a random number of children and randomly decide which
18 # children have children of their own.
19 nrChildren = random.randint(1, 6)
20 for childIndex in range(nrChildren):
21 child = self.AppendItem(event.GetItem(), 'child %d'%childIndex)
22 self.SetItemHasChildren(child, random.choice([True, False]))
23
24 def OnCollapseItem(self, event):
25 # Be prepared, self.CollapseAndReset below may cause
26 # another wx.EVT_TREE_ITEM_COLLAPSING event being triggered.
27 if self.__collapsing:
28 event.Veto()
29 else:
30 self.__collapsing = True
31 item = event.GetItem()
32 self.CollapseAndReset(item)
33 self.SetItemHasChildren(item)
34 self.__collapsing = False
35
36
37 class LazyTreeFrame(wx.Frame):
38 def __init__(self, *args, **kwargs):
39 super(LazyTreeFrame, self).__init__(*args, **kwargs)
40 self.__tree = LazyTree(self)
41
42
43 if __name__ == "__main__":
44 app = wx.App(False)
45 frame = LazyTreeFrame(None)
46 frame.Show()
47 app.MainLoop()
Lazy Evaluation Directory Browser Example
This is a demonstration of maintaining a directory listing tree. It uses lazy evaluation to update itself when a directory is expanded out. Doing a full evaluation on application startup takes 20+ seconds on my computer, whereas this is nearly instantaneous and minimal. I will update this code as I refine it, remove some of the kludges, and refactor it in a nicer way.
I made the code as verbose and explanatory as possible. The only thing a beginning wxPython developer might wonder about is the tree.GetPyData and tree.SetPyData functions. These let you associate an arbitrary python object with the item ID of a wxPython object. In this program the object is a 2-tuple containing the full directory path and a boolean indicator of whether that entry has been crawled and expanded before. A example call is:
# store the path information and that it has been previously expanded tree.SetPyData(rootID, ('c:\\', True))
Anyway, on to the sample application:
1 #!/usr/bin/env python
2
3 import wx
4 import os.path, dircache
5
6 class MyFrame(wx.Frame):
7 def __init__(self, *args, **kwds):
8 # this is just setup boilerplate
9 kwds["style"] = wx.DEFAULT_FRAME_STYLE
10 wx.Frame.__init__(self, *args, **kwds)
11
12 # our tree object, self.tree
13 self.tree = wx.TreeCtrl(self, -1, style=wx.TR_HAS_BUTTONS|wx.TR_DEFAULT_STYLE|wx.SUNKEN_BORDER)
14
15 sizer = wx.BoxSizer(wx.VERTICAL)
16 sizer.Add(self.tree, 1, wx.EXPAND, 0)
17 self.SetAutoLayout(True)
18 self.SetSizer(sizer)
19 sizer.Fit(self)
20 sizer.SetSizeHints(self)
21 self.Layout()
22
23 # register the self.onExpand function to be called
24 wx.EVT_TREE_ITEM_EXPANDING(self.tree, self.tree.GetId(), self.onExpand)
25 # initialize the tree
26 self.buildTree('/')
27
28 def onExpand(self, event):
29 '''onExpand is called when the user expands a node on the tree
30 object. It checks whether the node has been previously expanded. If
31 not, the extendTree function is called to build out the node, which
32 is then marked as expanded.'''
33
34 # get the wxID of the entry to expand and check it's validity
35 itemID = event.GetItem()
36 if not itemID.IsOk():
37 itemID = self.tree.GetSelection()
38
39 # only build that tree if not previously expanded
40 old_pydata = self.tree.GetPyData(itemID)
41 if old_pydata[1] == False:
42 # clean the subtree and rebuild it
43 self.tree.DeleteChildren(itemID)
44 self.extendTree(itemID)
45 self.tree.SetPyData(itemID,(old_pydata[0], True))
46
47 def buildTree(self, rootdir):
48 '''Add a new root element and then its children'''
49 self.rootID = self.tree.AddRoot(rootdir)
50 self.tree.SetPyData(self.rootID, (rootdir,1))
51 self.extendTree(self.rootID)
52 self.tree.Expand(self.rootID)
53
54 def extendTree(self, parentID):
55 '''extendTree is a semi-lazy directory tree builder. It takes
56 the ID of a tree entry and fills in the tree with its child
57 subdirectories and their children - updating 2 layers of the
58 tree. This function is called by buildTree and onExpand methods'''
59
60 # This is something to work around, because Windows will list
61 # this directory but throw a WindowsError exception if you
62 # try to use the listdir() command on it. I need a better workaround
63 # for this...this is a temporary kludge.
64 excludeDirs=["c:\\System Volume Information","/System Volume Information"]
65
66 # retrieve the associated absolute path of the parent
67 parentDir = self.tree.GetPyData(parentID)[0]
68
69
70 subdirs = dircache.listdir(parentDir)
71 subdirs.sort()
72 for child in subdirs:
73 child_path = os.path.join(parentDir,child)
74 if os.path.isdir(child_path) and not os.path.islink(child):
75 if child_path in excludeDirs:
76 continue
77 # add the child to the parent
78 childID = self.tree.AppendItem(parentID, child)
79 # associate the full child path with its tree entry
80 self.tree.SetPyData(childID, (child_path, False))
81
82 # Now the child entry will show up, but it current has no
83 # known children of its own and will not have a '+' showing
84 # that it can be expanded to step further down the tree.
85 # Solution is to go ahead and register the child's children,
86 # meaning the grandchildren of the original parent
87 newParent = child
88 newParentID = childID
89 newParentPath = child_path
90 newsubdirs = dircache.listdir(newParentPath)
91 newsubdirs.sort()
92 for grandchild in newsubdirs:
93 grandchild_path = os.path.join(newParentPath,grandchild)
94 if os.path.isdir(grandchild_path) and not os.path.islink(grandchild_path):
95 grandchildID = self.tree.AppendItem(newParentID, grandchild)
96 self.tree.SetPyData(grandchildID, (grandchild_path,False))
97
98 if __name__ == "__main__":
99 app = wx.PySimpleApp(0)
100 wx.InitAllImageHandlers()
101 frame = MyFrame(None, -1, "")
102 app.SetTopWindow(frame)
103 frame.Show()
104 app.MainLoop()
-- Carl Scharenberg (carlscharenberg@yahoo.com)
Recursively building a directory list into a wxTreeCtrl
This is a series of functions that I wrote for my mp3 player program to import items into a wxTreeCtrl as a folder tree of sorts, it preserves folder structure and places the items in both a list and a dictionary (note, the mp3 player specific stuff still remains, but you should be able to get the gist of the functions from it)
1 def FMNUBuildFolder(self, event):
2
3 dlg = wxDirDialog(self)
4
5 try:
6 if dlg.ShowModal() == wxID_OK:
7 dir = dlg.GetPath()
8 self.root = ""
9 self.list.DeleteAllItems()
10 self.tree.DeleteAllItems()
11 self.musiclist = []
12 self.musiclistdict = {}
13 self.nummp3s = 0
14 self.numparsed = 0
15 self.StartBuildFromDir(dir)
16 self.BuildMusicDict()
17 self.ParseMusicList()
18 self.tree.Expand(self.root)
19 finally:
20 dlg.Destroy()
21
22 def StartBuildFromDir(self, dir):
23 rootname = os.path.split(dir)
24 self.root = self.tree.AddRoot(rootname[1])
25 self.rootdir = dir
26 self.BuildChildrenFromDir(self.root, dir)
27
28 def BuildChildrenFromDir(self, parent, dir):
29 dirlisting = os.listdir(dir)
30 for listing in dirlisting:
31 pathinquestion = os.path.join(dir, listing)
32 if os.path.isfile(pathinquestion):
33 extension = os.path.splitext(pathinquestion)
34 extension = extension[1]
35 if extension == ".mp3":
36 child = self.tree.AppendItem(parent, listing)
37 childdata = self.tree.GetItemData(child)
38 childdata.path = pathinquestion
39 id3info = ID3(pathinquestion)
40 mp3info = [id3info.get('TITLE', ""),
41 id3info.get('ARTIST', ""),
42 id3info.get('GENRE', ""),
43 id3info.get('YEAR', ""),
44 id3info.get('ALBUM', ""),
45 id3info.get('TRACKNUMBER', ""),
46 pathinquestion]
47 self.musiclist.append(mp3info)
48 self.nummp3s += 1
49 elif os.path.isdir(pathinquestion):
50 newparent = self.tree.AppendItem(parent, listing)
51 newdir = os.path.join(dir, listing)
52 self.BuildChildrenFromDir(newparent, newdir)
53
54 def ParseMusicList(self):
55 items = self.musiclistdict.items()
56 for i, x in enumerate(items):
57 key, data = x
58 self.list.InsertStringItem(i, data[0])
59 self.list.SetStringItem(i, 0, data[0])
60 self.list.SetStringItem(i, 1, data[1])
61 self.list.SetStringItem(i, 2, data[2])
62 self.list.SetStringItem(i, 3, data[3])
63 self.list.SetStringItem(i, 4, data[4])
64 self.list.SetStringItem(i, 5, data[5])
65 self.list.SetStringItem(i, 6, data[6])
66 self.list.SetItemData(i, key)
67
68 def BuildMusicDict(self):
69 key = 0
70 for mp3listing in self.musiclist:
71 key += 1
72 newmp3listing = tuple(mp3listing)
73 self.musiclistdict[key] = newmp3listing
74 self.itemDataMap = self.musiclistdict
75 wxColumnSorterMixin.__init__(self, 7)
- Jeff Peck (Sanguinus@earthlink.net)
Building a tree non-recursively
Here's another approach which doesn't use a recursively function. For large trees you probably wants to add logic which removes items that no longer will be used from the ids dict. But it works nicely for a small directory structure. Don't forget to import os.path.
1 root = '/path/to/root'
2 ids = {root : tree.AddRoot(root)}
3 for (dirpath, dirnames, filenames) in os.walk(root):
4 for dirname in dirnames:
5 fullpath = os.path.join(dirpath, dirname)
6 ids[fullpath] = tree.AppendItem(ids[dirpath], dirname)
7 for filename in sorted(filenames):
8 tree.AppendItem(ids[dirpath], filename)
- Örjan Persson
Comments...
I will add a example for dragging items in a wxTreeCtrl and what is more interresting how to drag a branch in a wxTreeCtrl. I'll try my best but i'm a german kid and my english (espacially my grammar) is horrible. So i'll be very lucky if someone would parse my written lines after adding here and translate it in -real- english.
--René Freund
Drag and Drop with a wxTreeCtrl
Here's something that messes with drag-and-drop for a wxTreeCtrl. It allows the drag of "Series" nodes onto "Collection" nodes only and the drag of "Collection" nodes onto "Series" nodes only. It implements the DropSource's GiveFeedback method to provide the user with information about whether or not there is a legal drop at the cursor's current position.
NOTE: This code only works on Windows under wxPython 2.4.1.2. I see three issues when I try to run it on the Mac OS-X version of wxPython:
1. In the OnBeginDrag method, the line:
1 sel_item, flags = self.tree.HitTest(event.GetPoint())
does not work because of a problem with event.GetPoint(). This can be replaced with the following:
1 # Get the Mouse Position on the Screen
2 (windowx, windowy) = wx.wxGetMousePosition()
3 # Translate the Mouse's Screen Position to the Mouse's Control Position
4 (x, y) = self.tree.ScreenToClientXY(windowx, windowy)
5 # Now use the tree's HitTest method to find out about the potential drop target for the current mouse position
6 (sel_item, flags) = self.tree.HitTest((x, y))
2. The DropSource GiveFeedback() method only gets called once, rather than repeatedly when the mouse moves. This means that feedback is not given as it should be.
3. If the DropTarget's OnData() method vetos the data drop, that veto is not passed back to the originating OnBeginDrag method, which is therefore unaware that the drag has been vetoed.
See also: TreeCtrlDnD which does work under something other than Windows.
1 # This example is provided with no promises or warranties regarding accuracy or suitability for any purpose whatsoever.
2
3 """ -- DragandDrop.pyw --
4 A sample program demonstrating a Drag and Drop implementation within a wxTreeCtrl.
5 The wxTreeCrtl shows two types of nodes, Series nodes and Collection nodes.
6 This implementation allows users to drag Series nodes onto Collection nodes and
7 Collection nodes onto Series nodes, but it blocks dragging nodes onto the same types.
8 In my application, dragging certain types of nodes onto other types of nodes is
9 often a meaningful action. It does not always change the tree structure, but it
10 often causes the copying or movement of data behind the scenes. """
11
12
13 __author__ = 'David K. Woods, Ph.D., Wisconsin Center for Education Research, University of Wisconsin, Madison, dwoods at wcer dot wisc dot edu'
14
15 # Import wxPython
16 from wxPython import wx
17 # use the fast cPickle tool instead of the regular Pickle
18 import cPickle
19
20
21 # Declare the class for the main application Dialog Window
22 class MainWindow(wx.wxDialog):
23 """ This window displays a Tree Control that processes drag-and-drop activities. """
24 def __init__(self,parent,id,title):
25 # Create a Dialog Box
26 wx.wxDialog.__init__(self,parent,-h4, title, size = (320,600), style=wx.wxDEFAULT_FRAME_STYLE|wx.wxNO_FULL_REPAINT_ON_RESIZE)
27 # Make the Dialog box white
28 self.SetBackgroundColour(wx.wxWHITE)
29
30 # Add a wxTreeCtrl to the Dialog box. Use Layout Constraints to position it in the Dialog.
31 lay = wx.wxLayoutConstraints()
32 lay.top.SameAs(self, wx.wxTop, 10) # Top margin of 10
33 lay.bottom.SameAs(self, wx.wxBottom, 30) # Bottom margin of 30, to leave room for a button
34 lay.left.SameAs(self, wx.wxLeft, 10) # Left margin of 10
35 lay.right.SameAs(self, wx.wxRight, 10) # Right margin of 10
36 self.tree = wx.wxTreeCtrl(self, -1, style=wx.wxTR_HAS_BUTTONS)
37 self.tree.SetConstraints(lay)
38
39 # Add a wxButton to the Dialog box. Use Layout Constraints to position it in the Dialog.
40 lay = wx.wxLayoutConstraints()
41 lay.top.SameAs(self, wx.wxBottom, -25) # Position the button at the bottom of the Dialog.
42 lay.centreX.SameAs(self, wx.wxCentreX) # Center the button horizontally
43 lay.height.AsIs()
44 lay.width.AsIs()
45 btn = wx.wxButton(self, wx.wxID_OK, "OK")
46 btn.SetConstraints(lay)
47
48 # Add a root node to the wxTreeCtrl.
49 self.treeroot = self.tree.AddRoot('Transana Database')
50
51 # Add a Series Root and three subnodes
52 self.seriesroot = self.tree.AppendItem(self.treeroot, 'Series')
53 item = self.tree.AppendItem(self.seriesroot, 'Series 1')
54 item = self.tree.AppendItem(self.seriesroot, 'Series 2')
55 item = self.tree.AppendItem(self.seriesroot, 'Series 3')
56
57 # Add a Collection Root and three subnodes
58 self.collectionsroot = self.tree.AppendItem(self.treeroot, 'Collections')
59 item = self.tree.AppendItem(self.collectionsroot, 'Collection 1')
60 item = self.tree.AppendItem(self.collectionsroot, 'Collection 2')
61 item = self.tree.AppendItem(self.collectionsroot, 'Collection 3')
62
63 # Expand the nodes so everything is visible initially
64 self.tree.Expand(self.treeroot)
65 self.tree.Expand(self.seriesroot)
66 self.tree.Expand(self.collectionsroot)
67
68 # Define the Begin Drag Event for the tree
69 wx.EVT_TREE_BEGIN_DRAG(self.tree, self.tree.GetId(), self.OnBeginDrag)
70
71 # Define the Drop Target for the tree. The custom drop target object
72 # accepts the tree as a parameter so it can query it about where the
73 # drop is supposed to be occurring to see if it will allow the drop.
74 dt = TestDropTarget(self.tree)
75 self.tree.SetDropTarget(dt)
76
77 # Lay out the screen and maintain the present layout
78 self.Layout()
79 self.SetAutoLayout(wx.true)
80
81 # Show the Dialog modally
82 self.ShowModal()
83 # Destroy the Dialog when we are done with it
84 self.Destroy()
85
86
87 def OnBeginDrag(self, event):
88 """ Left Mouse Button initiates "Drag" for Tree Nodes """
89 # Items in the tree are not automatically selected with a left click.
90 # We must select the item that is initially clicked manually!!
91 # We do this by looking at the screen point clicked and applying the tree's
92 # HitTest method to determine the current item, then actually selecting the item
93 sel_item, flags = self.tree.HitTest(event.GetPoint())
94 self.tree.SelectItem(sel_item)
95
96 # Determine what Item is being "dragged", and grab it's data
97 tempStr = "%s" % (self.tree.GetItemText(sel_item))
98 print "A node called %s is being dragged" % (tempStr)
99
100 # Create a custom Data Object for Drag and Drop
101 ddd = TestDragDropData()
102 # In this case, the data is trivial, but this could easily be a more complex object.
103 ddd.SetSource(tempStr)
104
105 # Use cPickle to convert the data object into a string representation
106 pddd = cPickle.dumps(ddd, 1)
107
108 # Now create a wxCustomDataObject for dragging and dropping and
109 # assign it a custom Data Format
110 cdo = wx.wxCustomDataObject(wx.wxCustomDataFormat('TransanaData'))
111 # Put the pickled data object in the wxCustomDataObject
112 cdo.SetData(pddd)
113
114 # Sorry, I am not able to figure out how to extract data from a wxDataObjectComposite until after
115 # it's been dropped, so I'm ignoring that aspect of things for now. I've left the code commented
116 # out for others who wish to pursue it.
117
118 # Now put the CustomDataObject into a DataObjectComposite
119 # tdo = wx.wxDataObjectComposite()
120 # tdo.Add(cdo)
121
122 # Create a Custom DropSource Object. The custom drop source object
123 # accepts the tree as a parameter so it can query it about where the
124 # drop is supposed to be occurring to see if it will allow the drop.
125 tds = TestDropSource(self.tree)
126 # Associate the Data with the Drop Source Object
127 # I've associated both the pickled CustomDataObject and the raw data object here
128 # so that I can easily work with the data being dragged. I'm sure this is poor
129 # form and unnecessary, but there it is. You don't like it, you are welcome to fix it!
130 tds.SetData(cdo, ddd) # (tdo) would be used in place of (cdo) if I were using the DataObjectComposite object
131
132 # Initiate the Drag Operation
133 dragResult = tds.DoDragDrop(wx.true)
134
135 # Report the result of the final drop when everything else is completed by the other objects
136 if dragResult == wx.wxDragCopy:
137 print "Result indicated successful copy"
138 elif dragResult == wx.wxDragMove:
139 print "Result indicated successful move"
140 else:
141 print "Result indicated failed drop"
142 print
143
144
145 class TestDropSource(wx.wxDropSource):
146 """ This is a custom DropSource object designed to provide feedback to the user during the drag """
147 def __init__(self, tree):
148 # Create a Standard wxDropSource Object
149 wx.wxDropSource.__init__(self)
150 # Remember the control that initiate the Drag for later use
151 self.tree = tree
152
153 # SetData accepts an object (obj) that has been prepared for the DropSource SetData() method
154 # and a copy of the original data for internal use. I suppose I should rewrite this to
155 # just accept the original data and do the wxDataObject packaging here. Maybe when I have time.
156 def SetData(self, obj, originalData):
157 # Set the prepared object as the wxDropSource Data
158 wx.wxDropSource.SetData(self, obj)
159 # hold onto the original data for later use
160 self.data = originalData
161
162 # I want to provide the user with feedback about whether their drop will work or not.
163 def GiveFeedback(self, effect):
164 # This method does not provide the x, y coordinates of the mouse within the control, so we
165 # have to figure that out the hard way. (Contrast with DropTarget's OnDrop and OnDragOver methods)
166 # Get the Mouse Position on the Screen
167 (windowx, windowy) = wx.wxGetMousePosition()
168 # Translate the Mouse's Screen Position to the Mouse's Control Position
169 (x, y) = self.tree.ScreenToClientXY(windowx, windowy)
170 # Now use the tree's HitTest method to find out about the potential drop target for the current mouse position
171 (id, flag) = self.tree.HitTest((x, y))
172 # I'm using GetItemText() here, but could just as easily use GetPyData()
173 tempStr = self.tree.GetItemText(id)
174
175 # This line compares the data being dragged (self.data) to the potential drop site given by the current
176 # mouse position (tempStr). If we are going (from Series to Collection) or (from Collection to Series),
177 # we return FALSE to indicate that we should use the default drag-and-drop feedback, which will indicate
178 # that the drop is legal. If not, we return TRUE to indicate we are using our own feedback, which is
179 # implemented by changing the cursor to a "No_Entry" cursor to indicate the drop is not allowed.
180 # Note that this code here does not prevent the drop. That has to be implemented in the Drop Target
181 # object. It just provides visual feedback to the user.
182 if self.data.SourceText.startswith('Series') and tempStr.startswith('Collection') or \
183 self.data.SourceText.startswith('Collection') and tempStr.startswith('Series'):
184 # FALSE indicates that feedback is NOT being overridden, and thus that the drop is GOOD!
185 return wx.false
186 else:
187 # Set the cursor to give visual feedback that the drop will fail.
188 self.tree.SetCursor(wx.wxStockCursor(wx.wxCURSOR_NO_ENTRY))
189 # Setting the Effect to wxDragNone has absolutely no effect on the drop, if I understand this correctly.
190 effect = wx.wxDragNone
191 # returning TRUE indicates that the default feedback IS being overridden, thus that the drop is BAD!
192 return wx.true
193
194
195
196 class TestDragDropData(object):
197 """ This is a custom DragDropData object. It's pretty minimalist, but could be much more complex if you wanted. """
198 def __init__(self):
199 self.SourceText = ''
200
201 def __repr__(self):
202 return "SourceText = '%s'" % (self.SourceText)
203
204 def SetSource(self, txt):
205 self.SourceText = txt
206
207
208
209 class TestDropTarget(wx.wxPyDropTarget):
210 """ This is a custom DropTarget object designed to match drop behavior to the feedback given by the custom
211 Drag Object's GiveFeedback() method. """
212 def __init__(self, tree):
213 # use a normal wxPyDropTarget
214 wx.wxPyDropTarget.__init__(self)
215 # Remember the source Tree Control for later use
216 self.tree = tree
217
218 # specify the data formats to accept
219 self.df = wx.wxCustomDataFormat('TransanaData')
220 # Specify the data object to accept data for this format
221 self.cdo = wx.wxCustomDataObject(self.df)
222 # Set the DataObject for the PyDropTarget
223 self.SetDataObject(self.cdo)
224
225 def OnEnter(self, x, y, d):
226 # print "OnEnter %s, %s, %s" % (x, y, d)
227 # Just allow the normal wxDragResult (d) to pass through here
228 return d
229
230 def OnLeave(self):
231 # print "OnLeave"
232 pass
233
234 def OnDrop(self, x, y):
235 # Process the "Drop" event
236 # print "Drop: x=%s, y=%s" % (x, y)
237 # Use the tree's HitTest method to find out about the potential drop target for the current mouse position
238 (id, flag) = self.tree.HitTest((x, y))
239 # I'm using GetItemText() here, but could just as easily use GetPyData()
240 tempStr = self.tree.GetItemText(id)
241 # Remember the Drop Location for later Processing (in OnData())
242 self.DropLocation = tempStr
243 print "Dropped on %s." % tempStr
244 # Because the DropSource GiveFeedback Method can change the cursor, I find that I
245 # need to reset it to "normal" here or it can get stuck as a "No_Entry" cursor if
246 # a Drop is abandoned.
247 self.tree.SetCursor(wx.wxStockCursor(wx.wxCURSOR_ARROW))
248 # We don't yet have enough information to veto the drop, so return TRUE to indicate
249 # that we should proceed to the OnData method
250 return wx.true
251
252 # You can't know about the Source Data's characteristics in the OnDragOver method, so instead of
253 # doing any processing here to determine if a drop is legal or not, we use the DropSource's OnFeedback
254 # method and the DropTarget's OnData method to implement feedback and to allow or veto the drop
255 # respectively
256 # def OnDragOver(self, x, y, d):
257 # print "OnDragOver %s, %s, %s" % (x, y, d)
258 # I used to have a bunch of other logic here, but it could only know drop target information,
259 # and I needed to know about the Source of the Drag as well.
260
261 def OnData(self, x, y, d):
262 # once OnDrop returns TRUE, this method is automatically called.
263 # print "OnData %s, %s, %s" % (x, y, d)
264 # Let's get the data being dropped so we can do some processing logic
265 if self.GetData():
266 data = cPickle.loads(self.cdo.GetData())
267 print "Drop Data = '%s' dropped onto %s" % (data, self.DropLocation)
268
269 # This line compares the data being dragged (data) to the potential drop site determined in OnDrop and
270 # passed here as self.DropLocation. If we are going (from Series to Collection) or (from Collection to Series),
271 # we do nothing to veto the drop because the drop is legal. If not, we veto the drop by changing the wxDragResult
272 # variable to indicate the drop is not allowed.
273 # Note that this code here implements the drop logic and needs to match the logic in the DropSource's GiveFeedback()
274 # method. I should probably write a common function that both objects use to determine how to act.
275 if data.SourceText.startswith('Series') and self.DropLocation.startswith('Collection') or \
276 data.SourceText.startswith('Collection') and self.DropLocation.startswith('Series'):
277 # If we meet the criteria, we do nothing to interfere with the drop process
278 pass
279 else:
280 # If the test fails, we prevent the drop process by altering the wxDropResult (d)
281 d = wx.wxDragNone
282
283
284 # We can implement the desired behavior resulting from the drag and drop here!
285 # Note that this example intentionally does nothing more than give feedback via the print statement.
286 # If you want to experiment with having drag and drop alter the tree, be my guest.
287 if d == wx.wxDragCopy:
288 print "OnData Result indicated successful copy"
289 elif d == wx.wxDragMove:
290 print "OnData Result indicated successful move"
291 else:
292 print "OnData Result indicated failed drop"
293 return d
294
295
296
297 class MyApp(wx.wxApp):
298 def OnInit(self):
299 frame = MainWindow(None, -1, "Drag and Drop Test")
300 self.SetTopWindow(frame)
301 return wx.true
302
303
304 app = MyApp(0)
305 app.MainLoop()
- David Woods (dwoods at wcer dot wisc dot edu)
- Wisconsin Center for Education Research University of Wisconsin, Madison
Recursively building a list into a wxTreeCtrl (yet another sample)
This is a simple function I wrote to insert a list with variable depth into a treecontrol. I use this in my pyciv application where it builds a tree control that the user can click on to navigate faster.
1 #define the tree, the amount of children depends on the length of the list
2 _treeList = [
3 #project
4 (['Project', ['Geometries', 'Verticals']]),
5 #geometry
6 (['Geometry', ['Points', 'Layers', 'Water']]),
7 #soil
8 (['Soil', ['Soiltypes']]),
9 ]
10
11 #this function builds the list
12 def rec_filltreelist( self, element, child, firstChild ):
13 newchild = child
14 for e in element:
15 if isinstance( e, list ):
16 self.rec_filltreelist( e, newchild, firstChild )
17 else:
18 newchild = self.tree.AppendItem(child, e)
19 if not firstChild: firstChild = newchild
20
21 #and this is how you call the function
22 self.tree = wxTreeCtrl(splitter, tID, style=wxTR_HAS_BUTTONS|wxTR_HAS_VARIABLE_ROW_HEIGHT )
23 root = self.tree.AddRoot("Overview")
24 firstChild = None
25 self.rec_filltreelist( _treeList, root, None )
26 self.tree.Expand(root)
27
28 #always handy to attach some kind of event to this list
29 EVT_LEFT_DOWN (self.tree, self.OnTreeLeftDown)
30
31 #and here is how to find out what was called (could be done better I admit, but it does the job)
32 def OnTreeLeftDown(self, event):
33 pt = event.GetPosition();
34 item, flags = self.tree.HitTest(pt)
35 parent = self.tree.GetItemParent(item)
36 itemname = self.tree.GetItemText(item)
37 if itemname == 'Points':
38 self.ShowPointsDialog()
39 elif itemname == 'Layers':
40 self.ShowLayersDialog()
41 ...
42 event.Skip()
I hope this helps out someone!
Rob - delphiro dot zonnet dot nl
Simple Drag and Drop
I hunted around for quite a while and it was never quite made clear to me that if you want to use Drag and Drop to re-arrange a wxTreeCtrl, you've just got to make the tree respond to EVT_TREE_BEGIN_DRAG and EVT_TREE_END_DRAG in a simple way: call event.Allow() in Begin and then actually do the re-organization in End. If you add these lines to the wxPython/demo/wxTreeCtrl.py demo, you'll get an idea of what I mean and how simple this is. No drop targets.
1 def __init__(self, ...):
2
3 # These go at the end of __init__
4 self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnBeginDrag)
5 self.tree.Bind(wx.EVT_TREE_END_DRAG, self.OnEndDrag)
6
7 def OnBeginDrag(self, event):
8 '''Allow drag-and-drop for leaf nodes.'''
9 self.log.WriteText("OnBeginDrag")
10 if self.tree.GetChildrenCount(event.GetItem()) == 0:
11 event.Allow()
12 self.dragItem = event.GetItem()
13 else:
14 self.log.WriteText("Cant drag a node that has children")
15
16
17 def OnEndDrag(self, event):
18 '''Do the re-organization if possible'''
19
20 self.log.WriteText("OnEndDrag")
21 #If we dropped somewhere that isn't on top of an item, ignore the event
22 if not event.GetItem().IsOk():
23 return
24
25 # Make sure this memeber exists.
26 try:
27 old = self.dragItem
28 except:
29 return
30
31 # Get the other IDs that are involved
32 new = event.GetItem()
33 parent = self.tree.GetItemParent(new)
34 if not parent.IsOk():
35 return
36
37 # Move 'em
38 text = self.tree.GetItemText(old)
39 self.tree.Delete(old)
40 self.tree.InsertItem(parent, new, text)
Titus - titus a cs . ucr . edu
Drag and Drop with Folder Moving and Rearranging
By combining some recipes in the Wiki, along with the example in the wxPython Demo, I was able to come up with a fancy tree control.
DragAndDropWithFolderMovingAndRearranging
TreeCtrl with automatic scrolling while dragging
The following sample script demonstrates how to achieve automatic scrolling while dragging an item beyond the visible area of a tree control. In the sample only the lowest level items can be dragged.
Here's briefly how it works:
- In the DRAG_BEGIN event handler the tree binds to MOTION events.
- In the MOTION event handler, in case the mouse pointer leaves the tree control, a wx.Timer is started.
- The TIME event handler will scroll up/down one step in case the pointer is still outside the tree control and start a new wx.Timer. If not, the timer object is deleted. A new wx.Timer might be started from the MOTION event handler.
- The tree unregisters for the MOTION event when recieving a LEFT_UP event.
The methods that do the scrolling account for the differences between wxGTK and wxMSW. Note that automatic scrolling on wxMSW works only, if the tree control has an image list associated with it, thus showing little icons left to the text.
Christian
1 import wx
2
3 #---------------------------------------------------------------------------
4
5 class MyTreeCtrl(wx.TreeCtrl):
6 def __init__(self, parent, id, style):
7 wx.TreeCtrl.__init__(self, parent, id, style=style)
8
9 def OnCompareItems(self, item1, item2):
10 t1 = self.GetItemText(item1)
11 t2 = self.GetItemText(item2)
12 if t1 < t2: return -1
13 if t1 == t2: return 0
14 return 1
15
16 #---------------------------------------------------------------------------
17
18 class TestTreeCtrlPanel(wx.Panel):
19 def __init__(self, parent):
20 # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
21 wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
22 self.Bind(wx.EVT_SIZE, self.OnSize)
23
24 self.tree = MyTreeCtrl(self, -1, wx.TR_HAS_BUTTONS)
25
26 isz = (16,16)
27 il = wx.ImageList(isz[0], isz[1])
28 self.fldridx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, isz))
29 self.fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, isz))
30 self.fileidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, isz))
31
32 self.tree.SetImageList(il)
33 self.il = il
34
35 self.root = self.tree.AddRoot("The Root Item")
36 self.tree.SetPyData(self.root, None)
37 self.tree.SetItemImage(self.root, self.fldridx, wx.TreeItemIcon_Normal)
38 self.tree.SetItemImage(self.root, self.fldropenidx, wx.TreeItemIcon_Expanded)
39
40 for x in range(7):
41 child = self.tree.AppendItem(self.root, "Item %d" % x)
42 self.tree.SetPyData(child, None)
43 self.tree.SetItemImage(child, self.fldridx, wx.TreeItemIcon_Normal)
44 self.tree.SetItemImage(child, self.fldropenidx, wx.TreeItemIcon_Expanded)
45
46 for y in range(5):
47 last = self.tree.AppendItem(child, "item %d-%s" % (x, chr(ord("a")+y)))
48 self.tree.SetPyData(last, None)
49 self.tree.SetItemImage(last, self.fileidx, wx.TreeItemIcon_Normal)
50
51 self.tree.Expand(self.root)
52 self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree)
53
54 self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnDragBegin)
55 self.Bind(wx.EVT_TREE_END_DRAG, self.OnDragEnd)
56
57 self.Bind(wx.EVT_TIMER, self.OnTime)
58
59 def OnDragBegin(self, evt):
60 item = evt.GetItem()
61
62 if self.tree.GetItemParent(item) == self.root:
63 evt.Veto()
64 return
65
66 self.dragitem = item
67 evt.Allow()
68 self.tree.Bind(wx.EVT_MOTION, self.OnMotion)
69 self.tree.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
70
71 def OnDragEnd(self, evt):
72 target = evt.GetItem()
73
74 if not target.IsOk() or target == self.root:
75 return
76
77 text = self.tree.GetItemText(self.dragitem)
78 if self.tree.GetItemParent(target) == self.root:
79 added = self.tree.AppendItem(target, text)
80 else:
81 parent = self.tree.GetItemParent(target)
82 added = self.tree.InsertItemBefore(parent, self.findItem(target), text)
83
84 self.tree.Delete(self.dragitem)
85 self.tree.SetItemImage(added, self.fileidx, wx.TreeItemIcon_Normal)
86
87 self.tree.SelectItem(added)
88 self.tree.EnsureVisible(added)
89
90 def OnMouseLeftUp(self, evt):
91 self.tree.Unbind(wx.EVT_MOTION)
92 self.tree.Unbind(wx.EVT_LEFT_UP)
93 evt.Skip()
94
95 def OnMotion(self, evt):
96 size = self.tree.GetSize()
97 x,y = evt.GetPosition()
98
99 if y < 0 or y > size[1] and not hasattr(self, 'timer'):
100 self.timer = wx.Timer(self)
101 self.timer.Start(70)
102 evt.Skip()
103
104 def OnTime(self, evt):
105 x,y = self.tree.ScreenToClient(wx.GetMousePosition())
106 size = self.tree.GetSize()
107
108 if y < 0:
109 self.ScrollUp()
110 elif y > size[1]:
111 self.ScrollDown()
112 else:
113 del self.timer
114 return
115 self.timer.Start(70)
116
117 def ScrollUp(self):
118 if "wxMSW" in wx.PlatformInfo:
119 self.tree.ScrollLines(-1)
120 else:
121 first = self.tree.GetFirstVisibleItem()
122 prev = self.tree.GetPrevSibling(first)
123 if prev:
124 # drill down to find last expanded child
125 while self.tree.IsExpanded(prev):
126 prev = self.tree.GetLastChild(prev)
127 else:
128 # if no previous sub then try the parent
129 prev = self.tree.GetItemParent(first)
130
131 if prev:
132 self.tree.ScrollTo(prev)
133 else:
134 self.tree.EnsureVisible(first)
135
136 def ScrollDown(self):
137 if "wxMSW" in wx.PlatformInfo:
138 self.tree.ScrollLines(1)
139 else:
140 # first find last visible item by starting with the first
141 next = None
142 last = None
143 item = self.tree.GetFirstVisibleItem()
144 while item:
145 if not self.tree.IsVisible(item): break
146 last = item
147 item = self.tree.GetNextVisible(item)
148
149 # figure out what the next visible item should be,
150 # either the first child, the next sibling, or the
151 # parent's sibling
152 if last:
153 if self.tree.IsExpanded(last):
154 next = self.tree.GetFirstChild(last)[0]
155 else:
156 next = self.tree.GetNextSibling(last)
157 if not next:
158 prnt = self.tree.GetItemParent(last)
159 if prnt:
160 next = self.tree.GetNextSibling(prnt)
161
162 if next:
163 self.tree.ScrollTo(next)
164 elif last:
165 self.tree.EnsureVisible(last)
166
167 def traverse(self, parent=None):
168 if parent is None:
169 parent = self.root
170 nc = self.tree.GetChildrenCount(parent, False)
171
172 def GetFirstChild(parent, cookie):
173 return self.tree.GetFirstChild(parent)
174
175 GetChild = GetFirstChild
176 cookie = 1
177 for i in range(nc):
178 child, cookie = GetChild(parent, cookie)
179 GetChild = self.tree.GetNextChild
180 yield child
181
182 def findItem(self, item):
183 parent = self.tree.GetItemParent(item)
184 for n,i in enumerate(self.traverse(parent)):
185 if item == i:
186 return n
187
188 def OnSize(self, event):
189 w,h = self.GetClientSizeTuple()
190 self.tree.SetDimensions(0, 0, w, h)
191
192 def OnSelChanged(self, event):
193 self.item = event.GetItem()
194 event.Skip()
195
196
197 #---------------------------------------------------------------------------
198
199 def runTest(frame, nb, log):
200 win = TestTreeCtrlPanel(nb, log)
201 return win
202
203 def __test():
204
205 class MyApp(wx.App):
206 def OnInit(self):
207 wx.InitAllImageHandlers()
208 frame = wx.Frame(None, -1, 'Testframe', size=(200,400))
209 panel = TestTreeCtrlPanel(frame)
210 frame.Show()
211 return True
212
213
214 app = MyApp(0)
215 app.MainLoop()
216
217 if __name__ == '__main__':
218 __test()
The content on this page came from ListAndTreeControls, which was divided into this page and ListControls .