Eve Application Development

Michael L Brereton - 02 February 2008, http://www.ewesoft.com/

 

Beginners Guide - Contents

<< Previous: Table Controls

Tree Controls

Eve Application Development 1

Tree Controls. 1

Object Representation organization vs. Object Composition organization.. 1

Object Representation in Trees – Using eve.data.TreeNode. 1

Object Composition in Trees – Using the TreeModelAdapter Class. 4

 

 

This chapter gives information on using Tree UI controls. These are very powerful controls that can handle cut/paste operations as well as drag and drop operations. This chapter will discuss two methods of organizing your data so that it may be displayed in a tree control, as well as the methods needed to detect when the user interacts with the tree.

Object Representation organization vs. Object Composition organization

Trees, tables and lists are controls that display a range of data. There may be circumstances where this range of data is fairly small – say less than 100 data elements, or where the range is very large – say tens or hundreds of thousands of elements. In the case where the data range is small it may be convenient to have each element represented in memory as an Object organized in some sort of structure. For trees, this structure would be a tree type data structure, for tables a grid data structure would be appropriate, and for lists a single dimensional vector would be appropriate. This allows the programmer to create the entire structure once and then allow the control to access the various elements within the structure (depending on the view the user is interacting with) with no further programmer assistance. This approach can be thought of as Object Representation.

 

However when dealing with large ranges of data this approach will not be appropriate. In addition to the extended time required to read and create the structure, there will also be the problem of memory limitations. Generally in these cases, the actual data to be displayed would be stored on a local file system, and in these instances it is usually better to only read, decode and construct objects to represent the data when it is necessary to display that data on screen. Because only a small range of data is normally visible at a time, this approach usually works well at the expense of a slight delay in display refresh time. This approach can be thought of as on-demand Object Composition.

 

In the Eve UI library you can use either of these approaches when displaying data in trees, tables and lists, usually be inheriting from one of two base control classes. Tree and Table controls separate the “physical” user interface elements of the control from the data representation of the control. They do this by having a separate object representing the control (eve.ui.TableControl and eve.ui.TreeControl) and separate object representing the data (eve.ui.TableModel and eve.ui.TreeTableModel). You should note that TreeControl inherits from TableControl and TreeTableModel inherits from TableModel.

Object Representation in Trees – Using eve.data.TreeNode

This is very easy to do, since the basic TreeTableModel is geared towards easy display of tree data using the eve.data.TreeNode interface. This interface represents data elements that can have a single parent and several children. The interface consists of methods that allow a tree structure of such elements to be traversed and to be displayed in a TreeControl.

Node Traversing Methods

These include: int getChildCount(), TreeNode getChild(int childIndex) and  TreeNode getParent(). These are the primary methods used to go up and down a tree data structure consisting of TreeNode objects.

Tree Control Methods

These are methods that are primarily used by a TreeControl when displaying a tree data structure. They include:

boolean canExpand(), boolean isLeaf(), boolean expand() and boolean collapse()

 

isLeaf() tells the control whether to consider the TreeNode to be a leaf (which will not contain children) or a node (which may contain children). canExpand() tells the control whether or not it should display a ‘+’ symbol next to the node icon, used by the user to expand the node to display its children. expand() tells the node that the user has requested the node to be expand, and that the node, if necessary, should gather and organize its children. After calling this method, the methods getChildCount() and getChild() will be called to display the node’s children on-screen. This is useful for data structures that prefer to delay the gathering and construction of children until the user requests them to be displayed on-screen. Similarly, the collapse() method tells the node that the user has collapsed the node and that, if it wishes, it can free its children until the next expand() call.

The MutableTreeNode Interface

This interface is an extension of TreeNode and represents a data object that can have its children and parent manipulated. This is in contrast to TreeNode that only has methods to report the state of the tree data structure, but has none to modify it. Most implementations of a TreeNode will actually implement this interface as well.

The LiveTreeNode Object

This class is in the package eve.ui.data and is a full implementation of MutableTreeNode that also implements the eve.ui.data.LiveData object, a very useful application-oriented object. You can inherit from LiveTreeNode to build your data tree and then provide the TreeTableModel of the TreeControl that you are using with the root object of your data tree. The tree will then be viewable within the TreeControl.

 

Some methods you will want to override for this include:

String getName() – This returns the name of the object and will be displayed next to its icon.

IImage getIcon() – This should return a 16x16 image representing an icon to be displayed for the node. By default it returns null which indicates to the tree control that default images should be used.

boolean isLeaf() – By default this returns true if the node’s child count is zero.

boolean canExpand() – By default this returns true if the node’s child count is not zero.

Using TreeNode in a TreeControl

To set the root object in the tree control use TreeControl.getTreeTableModel() to get the TreeTableModel for the control and then call TreeTableModel.setRootObject(TreeNode root). After doing this you can then display the tree control, but remember to place it in a ScrollBarPanel like this:

 

TreeControl tc = new TreeControl();
Form f = new Form();
f.addLast(new ScrollBarPanel(tc));
tc.getTreeTableModel().setRootObject(myRootObject);

 

Displaying the form will then display the tree control with its associated data structure.

Lines and Indexes of a TreeControl

One of the properties of a TreeControl is that each row in the display represents exactly one node or leaf on-screen. This row is referred to as the line or index of the TreeControl (the two terms are used interchangeably). There is a direct mapping between a line and a single displayed node or leaf. Similarly, if a leaf or node is exposed on-screen, it is associated with a unique line/index.

 

There are several methods of TreeTableModel which use or return line/index values. In fact, this is the primary method of manipulating the TreeControl’s display. There are two methods that allow you to map TreeNode objects to line/index values.

 

int indexOf(TreeNode node)

This returns the line/index of the TreeNode if it is on-screen. If it is not (i.e. its parent has not been expanded) it will return –1.

TreeNode getTreeNodeAt(int index)

This returns the associated TreeNode at a particular index in the TreeControl.

 

Here are some useful TreeTableModel methods, most of which use line/index values.

 

void paintLine(int line)

This immediately repaints the specified line.

 

void reExpandNode(int line)

This causes the node on the specified line to collapse and be re-expanded. This is useful if you have made major changes to the child list of a particular node. However please note that the methods described in the section “Modifying the Tree Structure” may be more appropriate to update the display under some circumstances.

Modifying the Tree Structure

Modifying a tree of MutableTreeNode objects is very easy using the addChild() or removeChild() methods. However changes that you make to your object tree will not automatically be reflected in the TreeControl. In order for this to happen you must inform the tree control of what changes you have made so it will know which nodes and which children to refresh on-screen. Here are some TreeTableModel methods that you can use to update the display based on changes made to the tree data structure.

 

boolean inserted(TreeNode parent, TreeNode child, boolean selectChild)

You should call this method after you have inserted a new TreeNode as a child of a parent TreeNode. It prompts the display to re-expand the parent as needed to include the new child. You can set selectChild to be true if you want the new child to be automatically selected. This call re-organizes the tree controls internal data structure but does not refresh the display. You must call update() on the TreeTableModel to refresh the on-screen display.

 

boolean deleted(TreeNode parent, int indexOfDeletedChild)

You should call this method after you have deleted a child TreeNode from a parent TreeNode. It prompts the display to re-expand the parent as needed to include the new child. You must provide it with the index of the deleted child (i.e. its index when it was still a child of the parent). You must call update() on the TreeTableModel to refresh the on-screen display.

 

These two methods, inserted and deleted are the most memory and time efficient way to update the tree control when you have made changes to the underlying tree data structure. You can call them several times before calling update() since they do not refresh the on-screen display themselves.

 

Here is a simple example of using a TreeControl with TreeNodes.

 

package evesamples.ui;
 
import eve.sys.Event;
import eve.ui.Form;
import eve.ui.Label;
import eve.ui.ScrollBarPanel;
import eve.ui.table.TreeControl;
import eve.ui.table.TreeEvent;
 
public class TestTrees extends Form{
 
     TreeControl tree;
     Label txt;
     public TestTrees()
     {
             title = "Tree Control with Nodes";
             maximizeOnPDA();
             PersonTreeNode home = new PersonTreeNode("Springfield");
             home.addOneChild(
                            new PersonTreeNode("Abe").addOneChild(
                                           new PersonTreeNode("Homer")
                                           .addOneChild(new PersonTreeNode("Bart"))
                                           .addOneChild(new PersonTreeNode("Lisa"))
                                           .addOneChild(new PersonTreeNode("Maggie"))
                                           )
             );
             tree = new TreeControl();
             tree.getTreeTableModel().setRootObject(home);
             addLast(new ScrollBarPanel(tree));
             addLast(txt = new Label(" ")).setCell(HSTRETCH);
             setPreferredSize(200, 200);
     }
     
     public void onEvent(Event ev)
     {
             if (ev instanceof TreeEvent && ev.target == tree){
                     int i = tree.getSelectedLine();
                     if (i == -1) txt.setText("No selection.");
                     else{
                            String s = i+" = ";
                            PersonTreeNode pt = (PersonTreeNode)tree.getTreeTableModel().getTreeNodeAt(i);
                            s += pt.getName();
                            txt.setText(s);
                     }
             }else
                     super.onEvent(ev);
     }
}

 

 

Object Composition in Trees – Using the TreeModelAdapter Class

The TreeModelAdapter class is an extension of the TreeTableModel class that you can use to display tree data that do not need to be organized in a structure of TreeNode objects. The way this class works is as follows:

 

 

Here are the methods that must be overridden:

 

Object createObjectFor(Object parent, int childIndex)

This requests a new Object to represent the data of the child at the specified index in the specified parent. The only time parent will ever be null is when an object is being created for the root of the tree. In this case childIndex will only be 0.

 

int getChildCount(Object parent)

This is used to get the number of children for a particular parent node.

 

String getDisplayString(Object parent, int childIndex)

This is used to get the name to display for a particular child node.

 

These three are the absolute minimum that you should override to display your tree data. However the effect of only overriding this would be:

  1. All nodes would have the same icon (folders).
  2. All nodes would be considered expandable.

 

Two important methods that you can override are:

 

IImage getIcon(Object parent, int childIndex)

This returns the icon for a particular child of a parent node. By default this returns null.

 

void adjustFlags(Object parent, long [] indexes, byte [] flags)

The indexes and flags parameters are arrays each of the same length equal to the child count for the parent object. For each element in the flags array, you can switch on or off the bit values IsNode and CanExpand. This will tell the model whether the child at a particular index in the parent is a node or a leaf, and whether it can be expanded or not. The method by default leaves the flags unaltered, each one with the IsNode and CanExpand bits set.

 

There is an alternative to using the adjustFlags() method. If you set the dynamicCanExpand member of the TreeModelAdapter to be true, then the flags for each node/leaf in the display will be queried each time it is displayed. This results in a call to:

 

byte getFlags(Object parent, int childIndex, byte savedFlags)

This should return an adjusted value of savedFlags with the bits IsNode and CanExpand set on or off appropriately. This method is only called if dynamicCanExpand is true.