2 Tutorial Example : a simple word processing application

The following examples describes how to use the TreeNode class with Tk and QTk. The last example shows how an application can take full control over the tree.

2.1 Example using the Tk module

declare 
[Tree]={Module.link ["Tree.ozf"]}
TreeNode=Tree.treeNode
T={New Tk.toplevel tkInit}
C={New Tk.canvas tkInit(parent:T)}
{Tk.send pack(C)}
RootNode={New TreeNode init(canvas:C
                            font:"Helvetica 10" 
                            height:18
                            label:"Node" 
                           )}
{RootNode draw(x:2 y:2 height:_)}
 
proc{AddNodes Node Nu}
   Label={Node get(label:$)}
in 
   {List.forAllInd {List.make Nu}
    proc{$ I _}
       NewNode={New TreeNode init(parent:Node
                                  label:Label#" "#I)}
    in 
       {Node addLeaf(node:NewNode)}
       {AddChildren NewNode Nu-1}
    end}
end 
    
{AddNodes RootNode 5}
{RootNode expand}

The root node of the tree is an instance of TreeNode where the canvas parameter has been set. As shown in the example, a font can be specified by the font parameter. Each node of the tree will take the height that is the height of the font used to display the label of node, unless the height parameter force another value. The label parameter sets the text displayed in the node.

Once the root node is created, it is not yet shown in the canvas. You must explicitly specify where you want to display this node the calling the method show, hence :

{RootNode draw(x:2 y:2 height:_)}

The tree parameters x, y and height are required. The first two corresponds to the position where to place the top left corner of the node. The height parameter binds the specified variable to the height taken to draw the whole node (including subnodes if needed). In this case, this value can be ignored, hence _.

The procedure AddNodes recursively add nodes to a node. Adding a node is done in two steps :

2.2 Example using the QTk module

declare 
[Tree QTk]={Module.link ["Tree.ozf" "http://www.info.ucl.ac.be/people/ned/qtk/QTk.ozf"]}
TreeNode=Tree.treeNode
C
{{QTk.build td(canvas(glue:nswe
                      handle:C
                      tdscrollbar:true 
                      lrscrollbar:true))} show}
 
RootNode={New TreeNode init(canvas:C
                            font:{QTk.newFont font(family:"Helvetica" size:10)}
                            height:18
                            label:"Node" 
                           )}
{RootNode draw(x:2 y:2 height:_)}
 
proc{AddChildren Node Nu}
   Label={Node get(label:$)}
in 
   {List.forAllInd {List.make Nu}
    proc{$ I _}
       NewNode={New TreeNode init(parent:Node
                                  label:Label#" "#I)}
    in 
       {Node addLeaf(node:NewNode)}
       {AddChildren NewNode Nu-1}
    end}
end 
    
{AddChildren RootNode 5}
{RootNode expand}

As shown in this example, only the way of building the window is changed. Warning : there is no parameter checking in the TreeNode as is in QTk. If you use an invalid value for the underlying toolkit, the error will not be raised at the Oz level, but will appear at the standard error as a Tcl/Tk error.

2.3 Advanced interactions

This example will show how to bind events to user clicks on the nodes, how to manage a selection rectangle and how to make to tree be calculated lazily as the user expands the different nodes.

declare 
[Tree QTk]={Module.link ["Tree.ozf" "http://www.info.ucl.ac.be/people/ned/qtk/QTk.ozf"]}
TreeNode=Tree.treeNode
C
{{QTk.build td(canvas(glue:nswe
                      handle:C
                      tdscrollbar:true 
                      lrscrollbar:true))} show}
 
 
class CustomNode 
   from TreeNode 
   feat 
      calc
      current:{NewCell unit}
   meth init(...)=M
      TreeNode,M
      {self bind(event:"<1>" 
                 action:self#SelectNode)}
   end 
   meth SelectNode 
      if {Access self.current}\=unit then 
         {{Access self.current} select(state:false)}
      end 
      if {Access self.current}==self then 
         {self switch}
      end 
      {Assign self.current self}
      {self select(state:true)}
   end 
   meth expand(...)=M
      if {IsFree self.calc} then 
         Label={self get(label:$)}
      in 
         {self addLeaf(
                  nodes:{List.map ["a" "b" "c" "d"]
                         fun{$ C}
                            {New CustomNode init(parent:self 
                                               label:Label#" "#C)}
                         end})}
         self.calc=unit 
      end 
      TreeNode,M
   end 
end 
 
CurrentNode={NewCell unit}
 
RootNode={New CustomNode init(canvas:C
                              font:"Helvetica 10" 
                              height:18
                              label:"Node" 
                             )}
 
{RootNode draw(x:2 y:2 height:_)}
    
{RootNode expand}

Overloading the TreeNode class allows to take full control of the tree. The bind call in the init method defines a method to be called when the user left clicks on the node. A node can be marked as selected by calling the method select(state:true). CustomNode uses a shared feature called current to store which node is currently selected. The selectNode method, that is called when the user clicks on the node, checks to see if a node was previously selected to remove its selection rectangle (select(state:false)), and mark the clicked node as selected, storing this information in self.current. Moreover, if the user clicks several times the same node, it automatically switch between the states expanded and collapsed thanks to a call to the switch method.

The expand method is overloaded so that children nodes are computed on-the-fly. The first time the node tries to expand itself, the calc feature is not yet bound. The children are then computed and added and calc is bound to unit.


Donatien Grolaux
Version 1.1.1 (20000613)