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:
import wx
import os.path, dircache
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
# this is just setup boilerplate
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
# our tree object, self.tree
self.tree = wx.TreeCtrl(self, -1, style=wx.TR_HAS_BUTTONS|wx.TR_DEFAULT_STYLE|wx.SUNKEN_BORDER)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.tree, 1, wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(sizer)
sizer.Fit(self)
sizer.SetSizeHints(self)
self.Layout()
# register the self.onExpand function to be called
wx.EVT_TREE_ITEM_EXPANDING(self.tree, self.tree.GetId(), self.onExpand)
# initialize the tree
self.buildTree('/')
def onExpand(self, event):
'''onExpand is called when the user expands a node on the tree
object. It checks whether the node has been previously expanded. If
not, the extendTree function is called to build out the node, which
is then marked as expanded.'''
# get the wxID of the entry to expand and check it's validity
itemID = event.GetItem()
if not itemID.IsOk():
itemID = self.tree.GetSelection()
# only build that tree if not previously expanded
old_pydata = self.tree.GetPyData(itemID)
if old_pydata[1] == False:
# clean the subtree and rebuild it
self.tree.DeleteChildren(itemID)
self.extendTree(itemID)
self.tree.SetPyData(itemID,(old_pydata[0], True))
def buildTree(self, rootdir):
'''Add a new root element and then its children'''
self.rootID = self.tree.AddRoot(rootdir)
self.tree.SetPyData(self.rootID, (rootdir,1))
self.extendTree(self.rootID)
self.tree.Expand(self.rootID)
def extendTree(self, parentID):
'''extendTree is a semi-lazy directory tree builder. It takes
the ID of a tree entry and fills in the tree with its child
subdirectories and their children - updating 2 layers of the
tree. This function is called by buildTree and onExpand methods'''
# This is something to work around, because Windows will list
# this directory but throw a WindowsError exception if you
# try to use the listdir() command on it. I need a better workaround
# for this...this is a temporary kludge.
excludeDirs=["c:\\System Volume Information","/System Volume Information"]
# retrieve the associated absolute path of the parent
parentDir = self.tree.GetPyData(parentID)[0]
subdirs = dircache.listdir(parentDir)
subdirs.sort()
for child in subdirs:
child_path = os.path.join(parentDir,child)
if os.path.isdir(child_path) and not os.path.islink(child):
if child_path in excludeDirs:
continue
# add the child to the parent
childID = self.tree.AppendItem(parentID, child)
# associate the full child path with its tree entry
self.tree.SetPyData(childID, (child_path, False))
# Now the child entry will show up, but it current has no
# known children of its own and will not have a '+' showing
# that it can be expanded to step further down the tree.
# Solution is to go ahead and register the child's children,
# meaning the grandchildren of the original parent
newParent = child
newParentID = childID
newParentPath = child_path
newsubdirs = dircache.listdir(newParentPath)
newsubdirs.sort()
for grandchild in newsubdirs:
grandchild_path = os.path.join(newParentPath,grandchild)
if os.path.isdir(grandchild_path) and not os.path.islink(grandchild_path):
grandchildID = self.tree.AppendItem(newParentID, grandchild)
self.tree.SetPyData(grandchildID, (grandchild_path,False))
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame = MyFrame(None, -1, "")
app.SetTopWindow(frame)
frame.Show()
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:
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:
- {{{# Get the Mouse Position on the Screen
(windowx, windowy) = wx.wxGetMousePosition() # Translate the Mouse's Screen Position to the Mouse's Control Position (x, y) = self.tree.ScreenToClientXY(windowx, windowy) # Now use the tree's HitTest method to find out about the potential drop target for the current mouse position (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 \
self.data.SourceText.startswith('Collection') and tempStr.startswith('Series'):
183 # FALSE indicates that feedback is NOT being overridden, and thus that the drop is GOOD!
184 return wx.false
185 else:
186 # Set the cursor to give visual feedback that the drop will fail.
187 self.tree.SetCursor(wx.wxStockCursor(wx.wxCURSOR_NO_ENTRY))
188 # Setting the Effect to wxDragNone has absolutely no effect on the drop, if I understand this correctly.
189 effect = wx.wxDragNone
190 # returning TRUE indicates that the default feedback IS being overridden, thus that the drop is BAD!
191 return wx.true
192
193
194
195 class TestDragDropData(object):
196 """ This is a custom DragDropData object. It's pretty minimalist, but could be much more complex if you wanted. """
197 def __init__(self):
198 self.SourceText = ''
199
200 def __repr__(self):
201 return "SourceText = '%s'" % (self.SourceText)
202
203 def SetSource(self, txt):
204 self.SourceText = txt
205
206
207
208 class TestDropTarget(wx.wxPyDropTarget):
209 """ This is a custom DropTarget object designed to match drop behavior to the feedback given by the custom
210 Drag Object's GiveFeedback() method. """
211 def __init__(self, tree):
212 # use a normal wxPyDropTarget
213 wx.wxPyDropTarget.__init__(self)
214 # Remember the source Tree Control for later use
215 self.tree = tree
216
217 # specify the data formats to accept
218 self.df = wx.wxCustomDataFormat('TransanaData')
219 # Specify the data object to accept data for this format
220 self.cdo = wx.wxCustomDataObject(self.df)
221 # Set the DataObject for the PyDropTarget
222 self.SetDataObject(self.cdo)
223
224 def OnEnter(self, x, y, d):
225 # print "OnEnter %s, %s, %s" % (x, y, d)
226 # Just allow the normal wxDragResult (d) to pass through here
227 return d
228
229 def OnLeave(self):
230 # print "OnLeave"
231 pass
232
233 def OnDrop(self, x, y):
234 # Process the "Drop" event
235 # print "Drop: x=%s, y=%s" % (x, y)
236 # Use the tree's HitTest method to find out about the potential drop target for the current mouse position
237 (id, flag) = self.tree.HitTest((x, y))
238 # I'm using GetItemText() here, but could just as easily use GetPyData()
239 tempStr = self.tree.GetItemText(id)
240 # Remember the Drop Location for later Processing (in OnData())
241 self.DropLocation = tempStr
242 print "Dropped on %s." % tempStr
243 # Because the DropSource GiveFeedback Method can change the cursor, I find that I
244 # need to reset it to "normal" here or it can get stuck as a "No_Entry" cursor if
245 # a Drop is abandoned.
246 self.tree.SetCursor(wx.wxStockCursor(wx.wxCURSOR_ARROW))
247 # We don't yet have enough information to veto the drop, so return TRUE to indicate
248 # that we should proceed to the OnData method
249 return wx.true
250
251 # You can't know about the Source Data's characteristics in the OnDragOver method, so instead of
252 # doing any processing here to determine if a drop is legal or not, we use the DropSource's OnFeedback
253 # method and the DropTarget's OnData method to implement feedback and to allow or veto the drop
254 # respectively
255 # def OnDragOver(self, x, y, d):
256 # print "OnDragOver %s, %s, %s" % (x, y, d)
257 # I used to have a bunch of other logic here, but it could only know drop target information,
258 # and I needed to know about the Source of the Drag as well.
259
260 def OnData(self, x, y, d):
261 # once OnDrop returns TRUE, this method is automatically called.
262 # print "OnData %s, %s, %s" % (x, y, d)
263 # Let's get the data being dropped so we can do some processing logic
264 if self.GetData():
265 data = cPickle.loads(self.cdo.GetData())
266 print "Drop Data = '%s' dropped onto %s" % (data, self.DropLocation)
267
268 # This line compares the data being dragged (data) to the potential drop site determined in OnDrop and
269 # passed here as self.DropLocation. If we are going (from Series to Collection) or (from Collection to Series),
270 # we do nothing to veto the drop because the drop is legal. If not, we veto the drop by changing the wxDragResult
271 # variable to indicate the drop is not allowed.
272 # Note that this code here implements the drop logic and needs to match the logic in the DropSource's GiveFeedback()
273 # method. I should probably write a common function that both objects use to determine how to act.
274 if data.SourceText.startswith('Series') and self.DropLocation.startswith('Collection') or \
data.SourceText.startswith('Collection') and self.DropLocation.startswith('Series'):
275 # If we meet the criteria, we do nothing to interfere with the drop process
276 pass
277 else:
278 # If the test fails, we prevent the drop process by altering the wxDropResult (d)
279 d = wx.wxDragNone
280
281
282 # We can implement the desired behavior resulting from the drag and drop here!
283 # Note that this example intentionally does nothing more than give feedback via the print statement.
284 # If you want to experiment with having drag and drop alter the tree, be my guest.
285 if d == wx.wxDragCopy:
286 print "OnData Result indicated successful copy"
287 elif d == wx.wxDragMove:
288 print "OnData Result indicated successful move"
289 else:
290 print "OnData Result indicated failed drop"
291 return d
292
293
294
295 class MyApp(wx.wxApp):
296 def OnInit(self):
297 frame = MainWindow(None, -1, "Drag and Drop Test")
298 self.SetTopWindow(frame)
299 return wx.true
300
301
302 app = MyApp(0)
303 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
import wx
#---------------------------------------------------------------------------
class MyTreeCtrl(wx.TreeCtrl):
def __init__(self, parent, id, style):
wx.TreeCtrl.__init__(self, parent, id, style=style)
def OnCompareItems(self, item1, item2):
t1 = self.GetItemText(item1)
t2 = self.GetItemText(item2)
if t1 < t2: return -1
if t1 == t2: return 0
return 1
#---------------------------------------------------------------------------
class TestTreeCtrlPanel(wx.Panel):
def __init__(self, parent):
# Use the WANTS_CHARS style so the panel doesn't eat the Return key.
wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.tree = MyTreeCtrl(self, -1, wx.TR_HAS_BUTTONS)
isz = (16,16)
il = wx.ImageList(isz[0], isz[1])
self.fldridx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, isz))
self.fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, isz))
self.fileidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, isz))
self.tree.SetImageList(il)
self.il = il
self.root = self.tree.AddRoot("The Root Item")
self.tree.SetPyData(self.root, None)
self.tree.SetItemImage(self.root, self.fldridx, wx.TreeItemIcon_Normal)
self.tree.SetItemImage(self.root, self.fldropenidx, wx.TreeItemIcon_Expanded)
for x in range(7):
child = self.tree.AppendItem(self.root, "Item %d" % x)
self.tree.SetPyData(child, None)
self.tree.SetItemImage(child, self.fldridx, wx.TreeItemIcon_Normal)
self.tree.SetItemImage(child, self.fldropenidx, wx.TreeItemIcon_Expanded)
for y in range(5):
last = self.tree.AppendItem(child, "item %d-%s" % (x, chr(ord("a")+y)))
self.tree.SetPyData(last, None)
self.tree.SetItemImage(last, self.fileidx, wx.TreeItemIcon_Normal)
self.tree.Expand(self.root)
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree)
self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnDragBegin)
self.Bind(wx.EVT_TREE_END_DRAG, self.OnDragEnd)
self.Bind(wx.EVT_TIMER, self.OnTime)
def OnDragBegin(self, evt):
item = evt.GetItem()
if self.tree.GetItemParent(item) == self.root:
evt.Veto()
return
self.dragitem = item
evt.Allow()
self.tree.Bind(wx.EVT_MOTION, self.OnMotion)
self.tree.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
def OnDragEnd(self, evt):
target = evt.GetItem()
if not target.IsOk() or target == self.root:
return
text = self.tree.GetItemText(self.dragitem)
if self.tree.GetItemParent(target) == self.root:
added = self.tree.AppendItem(target, text)
else:
parent = self.tree.GetItemParent(target)
added = self.tree.InsertItemBefore(parent, self.findItem(target), text)
self.tree.Delete(self.dragitem)
self.tree.SetItemImage(added, self.fileidx, wx.TreeItemIcon_Normal)
self.tree.SelectItem(added)
self.tree.EnsureVisible(added)
def OnMouseLeftUp(self, evt):
self.tree.Unbind(wx.EVT_MOTION)
self.tree.Unbind(wx.EVT_LEFT_UP)
evt.Skip()
def OnMotion(self, evt):
size = self.tree.GetSize()
x,y = evt.GetPosition()
if y < 0 or y > size[1] and not hasattr(self, 'timer'):
self.timer = wx.Timer(self)
self.timer.Start(70)
evt.Skip()
def OnTime(self, evt):
x,y = self.tree.ScreenToClient(wx.GetMousePosition())
size = self.tree.GetSize()
if y < 0:
self.ScrollUp()
elif y > size[1]:
self.ScrollDown()
else:
del self.timer
return
self.timer.Start(70)
def ScrollUp(self):
if "wxMSW" in wx.PlatformInfo:
self.tree.ScrollLines(-1)
else:
first = self.tree.GetFirstVisibleItem()
prev = self.tree.GetPrevSibling(first)
if prev:
# drill down to find last expanded child
while self.tree.IsExpanded(prev):
prev = self.tree.GetLastChild(prev)
else:
# if no previous sub then try the parent
prev = self.tree.GetItemParent(first)
if prev:
self.tree.ScrollTo(prev)
else:
self.tree.EnsureVisible(first)
def ScrollDown(self):
if "wxMSW" in wx.PlatformInfo:
self.tree.ScrollLines(1)
else:
# first find last visible item by starting with the first
next = None
last = None
item = self.tree.GetFirstVisibleItem()
while item:
if not self.tree.IsVisible(item): break
last = item
item = self.tree.GetNextVisible(item)
# figure out what the next visible item should be,
# either the first child, the next sibling, or the
# parent's sibling
if last:
if self.tree.IsExpanded(last):
next = self.tree.GetFirstChild(last)[0]
else:
next = self.tree.GetNextSibling(last)
if not next:
prnt = self.tree.GetItemParent(last)
if prnt:
next = self.tree.GetNextSibling(prnt)
if next:
self.tree.ScrollTo(next)
elif last:
self.tree.EnsureVisible(last)
def traverse(self, parent=None):
if parent is None:
parent = self.root
nc = self.tree.GetChildrenCount(parent, False)
def GetFirstChild(parent, cookie):
return self.tree.GetFirstChild(parent)
GetChild = GetFirstChild
cookie = 1
for i in range(nc):
child, cookie = GetChild(parent, cookie)
GetChild = self.tree.GetNextChild
yield child
def findItem(self, item):
parent = self.tree.GetItemParent(item)
for n,i in enumerate(self.traverse(parent)):
if item == i:
return n
def OnSize(self, event):
w,h = self.GetClientSizeTuple()
self.tree.SetDimensions(0, 0, w, h)
def OnSelChanged(self, event):
self.item = event.GetItem()
event.Skip()
#---------------------------------------------------------------------------
def runTest(frame, nb, log):
win = TestTreeCtrlPanel(nb, log)
return win
def __test():
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = wx.Frame(None, -1, 'Testframe', size=(200,400))
panel = TestTreeCtrlPanel(frame)
frame.Show()
return True
app = MyApp(0)
app.MainLoop()
if __name__ == '__main__':
__test()The content on this page came from ListAndTreeControls, which was divided into this page and ListControls .
