Michael L Brereton - 02 February 2008, http://www.ewesoft.com/
<<
Previous: Table Controls
Object
Representation organization vs. Object Composition organization
Object
Representation in Trees – Using eve.data.TreeNode
Object
Composition in Trees – Using the TreeModelAdapter Class
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.
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.
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.
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.
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.
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.
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.
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.
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 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);
}
}
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:
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.