## ## Edit at will ## <> == Tree Controls == wxTree''''''Ctrl Lazy evaluation (using expansion/scrolling events to load only the visible elements into the controls) and other stuff(look through the page) See also: WxTreeControlArrowKeyBug === Lazy Evaluation Tree Controls === The lazy tree demonstrates basic Lazy Evaluation operation for a wxTree''''''Ctrl. 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. {{{ #!python import wx, random class LazyTree(wx.TreeCtrl): ''' LazyTree is a simple "Lazy Evaluation" tree, that is, it only adds items to the tree view when they are needed.''' def __init__(self, *args, **kwargs): super(LazyTree, self).__init__(*args, **kwargs) self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnExpandItem) self.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self.OnCollapseItem) self.__collapsing = False root = self.AddRoot('root') self.SetItemHasChildren(root) def OnExpandItem(self, event): # Add a random number of children and randomly decide which # children have children of their own. nrChildren = random.randint(1, 6) for childIndex in range(nrChildren): child = self.AppendItem(event.GetItem(), 'child %d'%childIndex) self.SetItemHasChildren(child, random.choice([True, False])) def OnCollapseItem(self, event): # Be prepared, self.CollapseAndReset below may cause # another wx.EVT_TREE_ITEM_COLLAPSING event being triggered. if self.__collapsing: event.Veto() else: self.__collapsing = True item = event.GetItem() self.CollapseAndReset(item) self.SetItemHasChildren(item) self.__collapsing = False class LazyTreeFrame(wx.Frame): def __init__(self, *args, **kwargs): super(LazyTreeFrame, self).__init__(*args, **kwargs) self.__tree = LazyTree(self) if __name__ == "__main__": app = wx.App(False) frame = LazyTreeFrame(None) frame.Show() 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: {{{ #!python #!/usr/bin/env python 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) {{{ #!python def FMNUBuildFolder(self, event): dlg = wxDirDialog(self) try: if dlg.ShowModal() == wxID_OK: dir = dlg.GetPath() self.root = "" self.list.DeleteAllItems() self.tree.DeleteAllItems() self.musiclist = [] self.musiclistdict = {} self.nummp3s = 0 self.numparsed = 0 self.StartBuildFromDir(dir) self.BuildMusicDict() self.ParseMusicList() self.tree.Expand(self.root) finally: dlg.Destroy() def StartBuildFromDir(self, dir): rootname = os.path.split(dir) self.root = self.tree.AddRoot(rootname[1]) self.rootdir = dir self.BuildChildrenFromDir(self.root, dir) def BuildChildrenFromDir(self, parent, dir): dirlisting = os.listdir(dir) for listing in dirlisting: pathinquestion = os.path.join(dir, listing) if os.path.isfile(pathinquestion): extension = os.path.splitext(pathinquestion) extension = extension[1] if extension == ".mp3": child = self.tree.AppendItem(parent, listing) childdata = self.tree.GetItemData(child) childdata.path = pathinquestion id3info = ID3(pathinquestion) mp3info = [id3info.get('TITLE', ""), id3info.get('ARTIST', ""), id3info.get('GENRE', ""), id3info.get('YEAR', ""), id3info.get('ALBUM', ""), id3info.get('TRACKNUMBER', ""), pathinquestion] self.musiclist.append(mp3info) self.nummp3s += 1 elif os.path.isdir(pathinquestion): newparent = self.tree.AppendItem(parent, listing) newdir = os.path.join(dir, listing) self.BuildChildrenFromDir(newparent, newdir) def ParseMusicList(self): items = self.musiclistdict.items() for i, x in enumerate(items): key, data = x self.list.InsertStringItem(i, data[0]) self.list.SetStringItem(i, 0, data[0]) self.list.SetStringItem(i, 1, data[1]) self.list.SetStringItem(i, 2, data[2]) self.list.SetStringItem(i, 3, data[3]) self.list.SetStringItem(i, 4, data[4]) self.list.SetStringItem(i, 5, data[5]) self.list.SetStringItem(i, 6, data[6]) self.list.SetItemData(i, key) def BuildMusicDict(self): key = 0 for mp3listing in self.musiclist: key += 1 newmp3listing = tuple(mp3listing) self.musiclistdict[key] = newmp3listing self.itemDataMap = self.musiclistdict 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. {{{ #!python root = '/path/to/root' ids = {root : tree.AddRoot(root)} for (dirpath, dirnames, filenames) in os.walk(root): for dirname in dirnames: fullpath = os.path.join(dirpath, dirname) ids[fullpath] = tree.AppendItem(ids[dirpath], dirname) for filename in sorted(filenames): tree.AppendItem(ids[dirpath], filename) }}} - Örjan Persson ==== Comments... ==== I will add a example for dragging items in a wxTree''''''Ctrl and what is more interresting how to drag a '''branch''' in a wxTree''''''Ctrl. 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 wxTree''''''Ctrl. It allows the drag of "Series" nodes onto "Collection" nodes only and the drag of "Collection" nodes onto "Series" nodes only. It implements the Drop''''''Source's Give''''''Feedback 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 wx''''''Python 2.4.1.2. I see three issues when I try to run it on the Mac OS-X version of wx''''''Python: 1. In the On''''''Begin''''''Drag method, the line: {{{ #!python sel_item, flags = self.tree.HitTest(event.GetPoint()) }}} does not work because of a problem with event.Get''''''Point(). This can be replaced with the following: {{{ #!python # 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 Drop''''''Source Give''''''Feedback() 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 Drop''''''Target's On''''''Data() method vetos the data drop, that veto is not passed back to the originating On''''''Begin''''''Drag method, which is therefore unaware that the drag has been vetoed. See also: [[TreeCtrlDnD]] which does work under something other than Windows. {{{ #!python # This example is provided with no promises or warranties regarding accuracy or suitability for any purpose whatsoever. """ -- DragandDrop.pyw -- A sample program demonstrating a Drag and Drop implementation within a wxTreeCtrl. The wxTreeCrtl shows two types of nodes, Series nodes and Collection nodes. This implementation allows users to drag Series nodes onto Collection nodes and Collection nodes onto Series nodes, but it blocks dragging nodes onto the same types. In my application, dragging certain types of nodes onto other types of nodes is often a meaningful action. It does not always change the tree structure, but it often causes the copying or movement of data behind the scenes. """ __author__ = 'David K. Woods, Ph.D., Wisconsin Center for Education Research, University of Wisconsin, Madison, dwoods at wcer dot wisc dot edu' # Import wxPython from wxPython import wx # use the fast cPickle tool instead of the regular Pickle import cPickle # Declare the class for the main application Dialog Window class MainWindow(wx.wxDialog): """ This window displays a Tree Control that processes drag-and-drop activities. """ def __init__(self,parent,id,title): # Create a Dialog Box wx.wxDialog.__init__(self,parent,-h4, title, size = (320,600), style=wx.wxDEFAULT_FRAME_STYLE|wx.wxNO_FULL_REPAINT_ON_RESIZE) # Make the Dialog box white self.SetBackgroundColour(wx.wxWHITE) # Add a wxTreeCtrl to the Dialog box. Use Layout Constraints to position it in the Dialog. lay = wx.wxLayoutConstraints() lay.top.SameAs(self, wx.wxTop, 10) # Top margin of 10 lay.bottom.SameAs(self, wx.wxBottom, 30) # Bottom margin of 30, to leave room for a button lay.left.SameAs(self, wx.wxLeft, 10) # Left margin of 10 lay.right.SameAs(self, wx.wxRight, 10) # Right margin of 10 self.tree = wx.wxTreeCtrl(self, -1, style=wx.wxTR_HAS_BUTTONS) self.tree.SetConstraints(lay) # Add a wxButton to the Dialog box. Use Layout Constraints to position it in the Dialog. lay = wx.wxLayoutConstraints() lay.top.SameAs(self, wx.wxBottom, -25) # Position the button at the bottom of the Dialog. lay.centreX.SameAs(self, wx.wxCentreX) # Center the button horizontally lay.height.AsIs() lay.width.AsIs() btn = wx.wxButton(self, wx.wxID_OK, "OK") btn.SetConstraints(lay) # Add a root node to the wxTreeCtrl. self.treeroot = self.tree.AddRoot('Transana Database') # Add a Series Root and three subnodes self.seriesroot = self.tree.AppendItem(self.treeroot, 'Series') item = self.tree.AppendItem(self.seriesroot, 'Series 1') item = self.tree.AppendItem(self.seriesroot, 'Series 2') item = self.tree.AppendItem(self.seriesroot, 'Series 3') # Add a Collection Root and three subnodes self.collectionsroot = self.tree.AppendItem(self.treeroot, 'Collections') item = self.tree.AppendItem(self.collectionsroot, 'Collection 1') item = self.tree.AppendItem(self.collectionsroot, 'Collection 2') item = self.tree.AppendItem(self.collectionsroot, 'Collection 3') # Expand the nodes so everything is visible initially self.tree.Expand(self.treeroot) self.tree.Expand(self.seriesroot) self.tree.Expand(self.collectionsroot) # Define the Begin Drag Event for the tree wx.EVT_TREE_BEGIN_DRAG(self.tree, self.tree.GetId(), self.OnBeginDrag) # Define the Drop Target for the tree. The custom drop target object # accepts the tree as a parameter so it can query it about where the # drop is supposed to be occurring to see if it will allow the drop. dt = TestDropTarget(self.tree) self.tree.SetDropTarget(dt) # Lay out the screen and maintain the present layout self.Layout() self.SetAutoLayout(wx.true) # Show the Dialog modally self.ShowModal() # Destroy the Dialog when we are done with it self.Destroy() def OnBeginDrag(self, event): """ Left Mouse Button initiates "Drag" for Tree Nodes """ # Items in the tree are not automatically selected with a left click. # We must select the item that is initially clicked manually!! # We do this by looking at the screen point clicked and applying the tree's # HitTest method to determine the current item, then actually selecting the item sel_item, flags = self.tree.HitTest(event.GetPoint()) self.tree.SelectItem(sel_item) # Determine what Item is being "dragged", and grab it's data tempStr = "%s" % (self.tree.GetItemText(sel_item)) print "A node called %s is being dragged" % (tempStr) # Create a custom Data Object for Drag and Drop ddd = TestDragDropData() # In this case, the data is trivial, but this could easily be a more complex object. ddd.SetSource(tempStr) # Use cPickle to convert the data object into a string representation pddd = cPickle.dumps(ddd, 1) # Now create a wxCustomDataObject for dragging and dropping and # assign it a custom Data Format cdo = wx.wxCustomDataObject(wx.wxCustomDataFormat('TransanaData')) # Put the pickled data object in the wxCustomDataObject cdo.SetData(pddd) # Sorry, I am not able to figure out how to extract data from a wxDataObjectComposite until after # it's been dropped, so I'm ignoring that aspect of things for now. I've left the code commented # out for others who wish to pursue it. # Now put the CustomDataObject into a DataObjectComposite # tdo = wx.wxDataObjectComposite() # tdo.Add(cdo) # Create a Custom DropSource Object. The custom drop source object # accepts the tree as a parameter so it can query it about where the # drop is supposed to be occurring to see if it will allow the drop. tds = TestDropSource(self.tree) # Associate the Data with the Drop Source Object # I've associated both the pickled CustomDataObject and the raw data object here # so that I can easily work with the data being dragged. I'm sure this is poor # form and unnecessary, but there it is. You don't like it, you are welcome to fix it! tds.SetData(cdo, ddd) # (tdo) would be used in place of (cdo) if I were using the DataObjectComposite object # Initiate the Drag Operation dragResult = tds.DoDragDrop(wx.true) # Report the result of the final drop when everything else is completed by the other objects if dragResult == wx.wxDragCopy: print "Result indicated successful copy" elif dragResult == wx.wxDragMove: print "Result indicated successful move" else: print "Result indicated failed drop" print class TestDropSource(wx.wxDropSource): """ This is a custom DropSource object designed to provide feedback to the user during the drag """ def __init__(self, tree): # Create a Standard wxDropSource Object wx.wxDropSource.__init__(self) # Remember the control that initiate the Drag for later use self.tree = tree # SetData accepts an object (obj) that has been prepared for the DropSource SetData() method # and a copy of the original data for internal use. I suppose I should rewrite this to # just accept the original data and do the wxDataObject packaging here. Maybe when I have time. def SetData(self, obj, originalData): # Set the prepared object as the wxDropSource Data wx.wxDropSource.SetData(self, obj) # hold onto the original data for later use self.data = originalData # I want to provide the user with feedback about whether their drop will work or not. def GiveFeedback(self, effect): # This method does not provide the x, y coordinates of the mouse within the control, so we # have to figure that out the hard way. (Contrast with DropTarget's OnDrop and OnDragOver methods) # 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 (id, flag) = self.tree.HitTest((x, y)) # I'm using GetItemText() here, but could just as easily use GetPyData() tempStr = self.tree.GetItemText(id) # This line compares the data being dragged (self.data) to the potential drop site given by the current # mouse position (tempStr). If we are going (from Series to Collection) or (from Collection to Series), # we return FALSE to indicate that we should use the default drag-and-drop feedback, which will indicate # that the drop is legal. If not, we return TRUE to indicate we are using our own feedback, which is # implemented by changing the cursor to a "No_Entry" cursor to indicate the drop is not allowed. # Note that this code here does not prevent the drop. That has to be implemented in the Drop Target # object. It just provides visual feedback to the user. if self.data.SourceText.startswith('Series') and tempStr.startswith('Collection') or \ self.data.SourceText.startswith('Collection') and tempStr.startswith('Series'): # FALSE indicates that feedback is NOT being overridden, and thus that the drop is GOOD! return wx.false else: # Set the cursor to give visual feedback that the drop will fail. self.tree.SetCursor(wx.wxStockCursor(wx.wxCURSOR_NO_ENTRY)) # Setting the Effect to wxDragNone has absolutely no effect on the drop, if I understand this correctly. effect = wx.wxDragNone # returning TRUE indicates that the default feedback IS being overridden, thus that the drop is BAD! return wx.true class TestDragDropData(object): """ This is a custom DragDropData object. It's pretty minimalist, but could be much more complex if you wanted. """ def __init__(self): self.SourceText = '' def __repr__(self): return "SourceText = '%s'" % (self.SourceText) def SetSource(self, txt): self.SourceText = txt class TestDropTarget(wx.wxPyDropTarget): """ This is a custom DropTarget object designed to match drop behavior to the feedback given by the custom Drag Object's GiveFeedback() method. """ def __init__(self, tree): # use a normal wxPyDropTarget wx.wxPyDropTarget.__init__(self) # Remember the source Tree Control for later use self.tree = tree # specify the data formats to accept self.df = wx.wxCustomDataFormat('TransanaData') # Specify the data object to accept data for this format self.cdo = wx.wxCustomDataObject(self.df) # Set the DataObject for the PyDropTarget self.SetDataObject(self.cdo) def OnEnter(self, x, y, d): # print "OnEnter %s, %s, %s" % (x, y, d) # Just allow the normal wxDragResult (d) to pass through here return d def OnLeave(self): # print "OnLeave" pass def OnDrop(self, x, y): # Process the "Drop" event # print "Drop: x=%s, y=%s" % (x, y) # Use the tree's HitTest method to find out about the potential drop target for the current mouse position (id, flag) = self.tree.HitTest((x, y)) # I'm using GetItemText() here, but could just as easily use GetPyData() tempStr = self.tree.GetItemText(id) # Remember the Drop Location for later Processing (in OnData()) self.DropLocation = tempStr print "Dropped on %s." % tempStr # Because the DropSource GiveFeedback Method can change the cursor, I find that I # need to reset it to "normal" here or it can get stuck as a "No_Entry" cursor if # a Drop is abandoned. self.tree.SetCursor(wx.wxStockCursor(wx.wxCURSOR_ARROW)) # We don't yet have enough information to veto the drop, so return TRUE to indicate # that we should proceed to the OnData method return wx.true # You can't know about the Source Data's characteristics in the OnDragOver method, so instead of # doing any processing here to determine if a drop is legal or not, we use the DropSource's OnFeedback # method and the DropTarget's OnData method to implement feedback and to allow or veto the drop # respectively # def OnDragOver(self, x, y, d): # print "OnDragOver %s, %s, %s" % (x, y, d) # I used to have a bunch of other logic here, but it could only know drop target information, # and I needed to know about the Source of the Drag as well. def OnData(self, x, y, d): # once OnDrop returns TRUE, this method is automatically called. # print "OnData %s, %s, %s" % (x, y, d) # Let's get the data being dropped so we can do some processing logic if self.GetData(): data = cPickle.loads(self.cdo.GetData()) print "Drop Data = '%s' dropped onto %s" % (data, self.DropLocation) # This line compares the data being dragged (data) to the potential drop site determined in OnDrop and # passed here as self.DropLocation. If we are going (from Series to Collection) or (from Collection to Series), # we do nothing to veto the drop because the drop is legal. If not, we veto the drop by changing the wxDragResult # variable to indicate the drop is not allowed. # Note that this code here implements the drop logic and needs to match the logic in the DropSource's GiveFeedback() # method. I should probably write a common function that both objects use to determine how to act. if data.SourceText.startswith('Series') and self.DropLocation.startswith('Collection') or \ data.SourceText.startswith('Collection') and self.DropLocation.startswith('Series'): # If we meet the criteria, we do nothing to interfere with the drop process pass else: # If the test fails, we prevent the drop process by altering the wxDropResult (d) d = wx.wxDragNone # We can implement the desired behavior resulting from the drag and drop here! # Note that this example intentionally does nothing more than give feedback via the print statement. # If you want to experiment with having drag and drop alter the tree, be my guest. if d == wx.wxDragCopy: print "OnData Result indicated successful copy" elif d == wx.wxDragMove: print "OnData Result indicated successful move" else: print "OnData Result indicated failed drop" return d class MyApp(wx.wxApp): def OnInit(self): frame = MainWindow(None, -1, "Drag and Drop Test") self.SetTopWindow(frame) return wx.true app = MyApp(0) 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 [[http://pyciv.sourceforge.net/|pyciv]] application where it builds a tree control that the user can click on to navigate faster. {{{ #!python #define the tree, the amount of children depends on the length of the list _treeList = [ #project (['Project', ['Geometries', 'Verticals']]), #geometry (['Geometry', ['Points', 'Layers', 'Water']]), #soil (['Soil', ['Soiltypes']]), ] #this function builds the list def rec_filltreelist( self, element, child, firstChild ): newchild = child for e in element: if isinstance( e, list ): self.rec_filltreelist( e, newchild, firstChild ) else: newchild = self.tree.AppendItem(child, e) if not firstChild: firstChild = newchild #and this is how you call the function self.tree = wxTreeCtrl(splitter, tID, style=wxTR_HAS_BUTTONS|wxTR_HAS_VARIABLE_ROW_HEIGHT ) root = self.tree.AddRoot("Overview") firstChild = None self.rec_filltreelist( _treeList, root, None ) self.tree.Expand(root) #always handy to attach some kind of event to this list EVT_LEFT_DOWN (self.tree, self.OnTreeLeftDown) #and here is how to find out what was called (could be done better I admit, but it does the job) def OnTreeLeftDown(self, event): pt = event.GetPosition(); item, flags = self.tree.HitTest(pt) parent = self.tree.GetItemParent(item) itemname = self.tree.GetItemText(item) if itemname == 'Points': self.ShowPointsDialog() elif itemname == 'Layers': self.ShowLayersDialog() ... 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 wxTree''''''Ctrl, 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/wxTree''''''Ctrl.py demo, you'll get an idea of what I mean and how simple this is. No drop targets. {{{ #!python def __init__(self, ...): # These go at the end of __init__ self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnBeginDrag) self.tree.Bind(wx.EVT_TREE_END_DRAG, self.OnEndDrag) def OnBeginDrag(self, event): '''Allow drag-and-drop for leaf nodes.''' self.log.WriteText("OnBeginDrag") if self.tree.GetChildrenCount(event.GetItem()) == 0: event.Allow() self.dragItem = event.GetItem() else: self.log.WriteText("Cant drag a node that has children") def OnEndDrag(self, event): '''Do the re-organization if possible''' self.log.WriteText("OnEndDrag") #If we dropped somewhere that isn't on top of an item, ignore the event if not event.GetItem().IsOk(): return # Make sure this memeber exists. try: old = self.dragItem except: return # Get the other IDs that are involved new = event.GetItem() parent = self.tree.GetItemParent(new) if not parent.IsOk(): return # Move 'em text = self.tree.GetItemText(old) self.tree.Delete(old) 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 {{{ #!python 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 .