Hi there. I'm writing a program that uses a tree control as a form of navigation, and the structure of the tree is set up in code as the program loads.

This is fine for now, but there is the possibility that the structure will need to expand considerably, which means I'm going to want to load it from a file (and preferably be able to save it as well, but thats not so important just now).

I need some kind of file format that will allow me to load the complete tree structure from a file. Iv messed about with this for a while, and I can load *some* items, but things go badly wrong when I have children within children and so on.

Can anyone help at all please?

Regards

Max

I would suggest something like this, with the pickle module (completely untested)

import pickle

class Tree(object):
  # a tree class, with a single root (a Node object)
  def __init__(self):
    self.root = None
  def fillCtrl(self, ctrl):
    # fills an empty wx.TreeCtrl (ctrl) with the content of the tree structure
    if self.root is None:
      return
    root_item = ctrl.AddRoot(self.root.text)
    self.root.fillCtrl(ctrl, root_item)
  def dump(self, file):
    # writes the tree structure to a file using the pickle protocol
    pickle.dump(file, self)
  @staticmethod
  def load(file):
    # creates a new Tree which content is read in a file
    return pickle.load(file)
  @staticmethod readCtrl(ctrl):
    # creates a new Tree from the content of a wx.TreeCtrl
    self = Tree()
    item = ctrl.GetRootItem()
    self.root = Node()
    self.root.text = ctrl.GetItemText(item)
    self.root.readCtrl(ctrl, item)
    return self
    
class Node(list):
  # A class for nodes of Tree objects. Each node is a list which elements are
  # the subnodes
  def __init__(self):
    list.__init__(self)
    self.text = ""
  def fillCtrl(self, ctrl, parent_item):
    # fills a wx.TreeCtrl with items corresponding to the descent of this node
    for child in self:
      item = ctrl.AppendItem(parent_item, child.text)
      child.fillCtrl(ctrl, item)
  def readCtrl(self, ctrl, item):
    # creates the descent of this node from the content of a wx.TreeCtrl item
    child_item = ctrl.GetFirstChild(item)
    if child_item[0]:
      child_item = child_item[0]
    else:
      return
    while True:
      self.append(Node())
      self[-1].text = ctrl.GetItemText(child_item)
      self[-1].readCtrl(ctrl, child_item)
      child_item = ctrl.GetNextSibling(child_item)
      if not child_item:
        break


def dumpCtrl(ctrl, file):
  # Dumps the content of a wx.TreeCtrl to a opened file
  tree = Tree.readCtrl(ctrl)
  tree.dump(file)
  
def loadCtrl(ctrl, file):
  # loads a file containing the content of a wx.TreeCtrl
  tree = Tree.load(file)
  tree.fillCtrl(ctrl)

I think you could also use XML to save it. If you are using wxPython then there is an XML module you can load that makes it nice and easy to load.

The module is wx.xrc, it can load a panel, a frame or any other kind of object. Its great to have a fiddle with.

Thanks Gribouillis, but I cant get that code to work at all - some problem with the '@staticmethod'.

Thanks Paul, Ill look that up.

Regards

Max

Sorry, there is a syntax error in the code above. You should replace

@staticmethod readCtrl(ctrl):

by

@staticmethod
    def readCtrl(ctrl):

However, since the code is untested, it's most likely broken, but I think in principle it should store your tree control on the disk and back after you kill a few bugs :)

Thanks Gribouillis - I still cant get it to work, but I did find this during my searches:

If you are thinking of pickling/unpickling the wx.TreeCtrl, that won't
work. wxWidgets/wxPython objects are usually non-pickable (with few
exceptions, maybe), and I doubt they will ever be.

I found it here Link so I'm not sure that Pickle is the way to proceed.

I might have to work out how to enumerate the tree accurately and save to an XML file, although that presents challenges of its own!

Paul, I took at look at XRC, but I couldn't see how it would help much (In fact I didn't understand it much at all, beyond the fact that it can be used to separate the design from the code).

Max

I don't quite remember but I believe that treectrl structure can be loaded from a dictionary, right?

If that is correct then you can simply use pickle to store/load the dictionary.. which will save a few lines of code I guess...

Thanks jlm699, Iv not gotten round to dictionaries yet, but Ill take a look.

Max

Well! Now I know what dictionaries are! ;) However, I'm no closer to loading/saving the structure of a tree, in fact I managed to get my program to crash every time I tried!

I'm having trouble with enumerating the tree correctly and creating the dictionary with the results, and thus far I have failed to create a dictionary with the complete structure.

Having actually produced a dictionary, albeit without the complete tree structure, I managed to save it but I cant reload it into the tree at all.

Can anyone help with this please?

max

Okay, Iv managed to answer this myself, although the method is a bit of a workaround hack!

The code is below, but Ill explain quickly, the file that holds the structure of the tree is in the format:

"Root Item Name#Item Name"

This format, along with the way in which the file is loaded, would allow us to add other items of data to be related to each tree item.

Caveats:
The load routine is designed around the file that was output by the save routine! You can of course code this file by hand if you wish to!

Saving comes first because as the tree is saved it creates the correct file format for re-loading. Firstly I define three variables to hold some data later (These are used by both routines):

self.HoldBuffer1 = ""
self.HoldBuffer2 = ""
self.TROG = ""

Now then, lets assume that you have a button "btnSAVE":

#    
    def btnSAVE_OnClick(self, event): 
        #we can set this to root here because we want to
        #start from the first item
        self.HoldBuffer1 = "ROOT"
        #just as good housekeeping
        self.TROG = ""
        #call the routine that will build the data to
        #save
        self.tSaveTree(self.cTREE.GetRootItem())
        #save the data
        f = open( 'treestructure.dat', 'w')
        f.write(self.TROG)            
        f.close()
        event.Skip()

#
#
#
#
#
    def tSaveTree(self, treeNode): 
        #as we loop through the tree we add the relevant info
        #to TROG
        if self.HoldBuffer1 == "ROOT":
            self.TROG = self.TROG + "ROOT#" + self.cTREE.GetItemText(treeNode) + "\n"
            self.HoldBuffer1 = ""
        else:
            #because this is not the root item we need to
            #find the text of the parent item
            x = self.cTREE.GetItemParent(treeNode)
            b =  self.cTREE.GetItemText(x)
            self.TROG = self.TROG + b + "#" + self.cTREE.GetItemText(treeNode) + "\n"
        
        if self.cTREE.GetChildrenCount(treeNode, False):
            cookie = -1
            child, cookie = self.cTREE.GetFirstChild(treeNode)
            while child:
                self.tSaveTree(child)
                child, cookie = self.cTREE.GetNextChild(treeNode, cookie)
        
#

Okay, so now there is a file (called treestructure.dat), and we know what format it is in. So loading becomes fairly easy:

#
#    
    def btnLOAD_OnClick(self, event): 
        i = 0
        f = open( 'treestructure.dat', 'r')
        x = f.readline()
        #loop for as long as we have something in
        #the line
        while x > "":
            #remove the linefeed at the end of the line
            #or things go wrong 
            #KLeft is my own little function - you can
            #use any method you like as long as you
            #lose the linefeed!
            z = KLeft(x, len(x) - 1)
            #split the string
            dank = z.split('#')
            #if this is the first line we have read
            #then we know that it will be the root item
            if i == 0:
                #this is the root item
                troot = self.cTREE.AddRoot(dank[1])
            else:
                #dank 0 holds the text of the parent item
                self.HoldBuffer1 = dank[0]
                self.HoldBuffer2 = dank[1]
                self.tLoadTree(self.cTREE.GetRootItem())

            #increment this now for the next item
            i = i + 1
            #read the next line
            x = f.readline()
        #all done so close the file
        f.close()

#
#
#
#
#
    def tLoadTree(self, treeNode): 
        if self.HoldBuffer1 == self.cTREE.GetItemText(treeNode):
            self.cTREE.AppendItem(treeNode,self.HoldBuffer2)
            return
            
        if self.cTREE.GetChildrenCount(treeNode, False):
            cookie = -1
            child, cookie = self.cTREE.GetFirstChild(treeNode)
            while child:
                self.tLoadTree(child)
                child, cookie = self.cTREE.GetNextChild(treeNode, cookie)
#
#

All done! Like I said, it ain't pretty, but it works.

I would of course be happy for anyone to show me a faster, cleaner way of ending up with the same thing.

Regards

Max

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.