14 March 2003
Michael L Brereton
Object Representation organization vs. Object Composition
organization
Object Representation in Trees – Using ewe.data.TreeNode
Using TreeNode in a TreeControl
Lines and Indexes of a TreeControl
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 Ewe 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 (ewe.ui.TableControl
and ewe.ui.TreeControl) and separate object representing the data
(ewe.ui.TableModel and ewe.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 ewe.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
object is a full implementation of MutableTreeNode that also implements the
ewe.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.
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.