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

   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)


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:

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 .

TreeControls (last edited 2012-06-28 08:16:51 by SWM-HIGH-SPEED2-22)

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