drag and drop between multiple wxTreeCtrl trees, example program

Hi,

as I haven't found an example of using drag'n drop between multiple
wxTreeCtrl's, I'm posting a short example program, just in case that
someone else will have the same problem...
It also allows dnd between two programs, and dragging of files onto
wxTreeCtrl's.

- Ralf

···

========================
#! /usr/bin/env python

# Drag and Drop contributed by Sam Anderson, reposted by Dirk Krause

import string
import cPickle as pickle
from xml.parsers import expat

from wxPython.wx import *

wxInitAllImageHandlers()

class DropData(wxCustomDataObject):
    def __init__(self):
        wxCustomDataObject.__init__(self, wxCustomDataFormat("MyDropData"))
        self.setObject(None)
        
    def setObject(self, obj):
        self.SetData(pickle.dumps(obj))

    def getObject(self):
        return pickle.loads(self.GetData())

class MyDropTarget(wxPyDropTarget):
    def __init__(self, tree):
        wxPyDropTarget.__init__(self)
        self._makeObjects()
        self.tree = tree
        self.selections=[]

    def _makeObjects(self):
        self.data = DropData()
        self.fileObject = wxFileDataObject()
        comp = wxDataObjectComposite()
        comp.Add(self.data)
        comp.Add(self.fileObject)
        self.comp = comp
        self.SetDataObject(comp)
        
    def _saveSelection(self):
        self.selections = self.tree.GetSelections()
        self.tree.UnselectAll()

    def _restoreSelection(self):
        self.tree.UnselectAll()
        for i in self.selections:
            self.tree.SelectItem(i)
        self.selections=[]
        
    def OnEnter(self, x, y, d):
        self._saveSelection()
        return d

    def OnLeave(self):
        self._restoreSelection()

    def OnDrop(self, x, y):
        self._restoreSelection()
        #item, flags = self.tree.HitTest((x, y))

        print "got an drop event at", x, y
        return True

    def OnDragOver(self, x, y, d):
        # provide visual feedback by selecting the item the mouse is over
        item, flags = self.tree.HitTest((x,y))
        selections = self.tree.GetSelections()
        if item:
            if selections != [item]:
                self.tree.UnselectAll()
                self.tree.SelectItem(item)
        elif selections:
            self.tree.UnselectAll()
            
        # The value returned here tells the source what kind of visual
        # feedback to give. For example, if wxDragCopy is returned then
        # only the copy cursor will be shown, even if the source allows
        # moves. You can use the passed in (x,y) to determine what kind
        # of feedback to give. In this case we return the suggested value
        # which is based on whether the Ctrl key is pressed.
        return d

    # Called when OnDrop returns True. We need to get the data and
    # do something with it.
    def OnData(self, x, y, d):
        if self.GetData():
            filenames = self.fileObject.GetFilenames()
            data = self.data.getObject()

            print "====>",
            if filenames:
                print "files", filenames,
                
            if data:
                print "item", repr(data),
                
            item, flags = self.tree.HitTest((x,y))
            if item:
                print "dropped on item:", self.tree.GetItemText(item)
            else:
                print "dropped nowhere"

            self._makeObjects() # reset data objects..
            
        return d # what is returned signals the source what to do
                  # with the original data (move, copy, etc.) In this
                  # case we just return the suggested value given to us.

# dummy xml file
xml='''<?xml version="1.0" encoding="UTF-8"?>
<Test title="Channels">
<Channels title="Favourites">
  <Channel type="channel" title="Slashdot" uri="http://slashdot.org"/>
</Channels>
<Channels title="News">
  <Channel title="CNN" uri="http://cnn.com"/>
  <Channel title="BBC" uri="http://bbc.co.uk"/>
  <Channel title="The Economist" uri="http://economist.com"/>
  <Channel title="MSNB" uri="http://msnbc.com"/>
</Channels>
<Channels title="Sports">
  <Channel type="channel" title="ESPN" uri="http://espn.com"/>
</Channels>
<Trash title="Trash"/>
</Test>'''

class TestApp(wxApp):

    def OnInit(self):

        # create navigation frame
        frame = NavFrame(NULL, -1, 'wxTreeCtrl DragNDrop Test')
        for tree in frame.trees:
            # expand a few nodes
            # NOTE: I know this is pathetic but I couldn't figure out how
            # to walk through and expand all nodes
            root = tree.nodeStack[0]
            child, cookie = tree.GetFirstChild(root, 0)
            nchild, cookie = tree.GetFirstChild(child, 0)
            tree.Expand(root)
            tree.Expand(child)
            tree.Expand(nchild)

        # display navigation frame
        frame.Show(true)
        self.SetTopWindow(frame)
        return true

class NavFrame(wxFrame):

    def __init__(self, parent, ID, title):
        wxFrame.__init__(self, parent, ID, title,
           wxDefaultPosition,
           wxSize(650,400))
        self.ils = []
        self.trees = []
        self.newTree((20,20))
        self.newTree((300,20))
        # other events
        EVT_CLOSE(self, self.OnCloseWindow)

    def newTree(self, pos):
        # give the tree a unique id
        tID = wxNewId()

        # call the tree
        tree = XMLTree(self, tID, pos, wxSize(270,290), # wxDefaultPosition, wxSize(270,490),
             wxTR_HAS_BUTTONS | wxTR_MULTIPLE)# | wxTR_EDIT_LABELS)
        # Trees need an image list to do DnD...
        tree.SetImageList(self.MakeImageList())

        # Load XML into tree
        tree.LoadXML()

        md=MyDropTarget(tree)
        tree.SetDropTarget(md)
        
        EVT_TREE_BEGIN_DRAG(tree, tID, self.OnBeginDrag)
        self.trees.append(tree)
        
    def OnBeginDrag(self, event):
        item = event.GetItem()
        tree = event.GetEventObject()

        if item != tree.GetRootItem(): # prevent dragging root item
            def DoDragDrop():
                txt = tree.GetItemText(item)
                print "Starting drag'n'drop with %s..." % repr(txt)
                dd = DropData()
                dd.setObject(txt)

                comp = wxDataObjectComposite()
                comp.Add(dd)
                dropSource = wxDropSource(self)
                dropSource.SetData(comp)
                result = dropSource.DoDragDrop(wxDrag_AllowMove)
                print "drag'n'drop finished with:", result, "\n"
            
            wxCallAfter(DoDragDrop) # can't call dropSource.DoDragDrop here..

    def OnCloseWindow(self, event):
        self.Destroy()

    def MakeImageList(self):
        il = wxImageList(16, 16)
        bmp = wxBitmap("FolderNormal.gif", wxBITMAP_TYPE_GIF)
        il.Add(bmp)
        self.ils.append(il)
        return il

class XMLTree(wxTreeCtrl):
    def __init__(self, parent, id, pos, size, style):
        wxTreeCtrl.__init__(self, parent, id, pos, size, style)

        # create nodeStack and add root node
        self.nodeStack = [self.AddRoot("My InfoSpace")]

    def StartElement(self, name, attrs ):
        name = name.encode()

        id = self.AppendItem(self.nodeStack[-1], name)

        # for each element map xml attributes to an associated dictionary
        self.SetPyData(id, attrs)

        # set title of tree node based on element title attribute
        if attrs.has_key('title'):
            title = attrs['title']
            self.SetItemText(id, str(title))

        # add element to tree
        self.nodeStack.append(id)

    def EndElement(self, name ):
        self.nodeStack = self.nodeStack[:-1]

    def CharacterData(self, data ):
        if string.strip(data):
            data = ""
            self.AppendItem(self.nodeStack[-1], data)

    def LoadXML(self):
        Parser = expat.ParserCreate()

        Parser.StartElementHandler = self.StartElement
        Parser.EndElementHandler = self.EndElement
        Parser.CharacterDataHandler = self.CharacterData

        ParserStatus = Parser.Parse(xml, 1)

if __name__ == '__main__':
    app = TestApp(0)
    app.MainLoop()

--
brainbot technologies ag
boppstrasse 64 . 55118 mainz . germany
fon +49 6131 211639-1 . fax +49 6131 211639-2
http://brainbot.com/ mailto:ralf@brainbot.com