17.2 Tree Models


Looking at Figure 17-4 you can get an overview of where all the tree pieces come from. As with many of the Swing components you've seen already, the models supporting the data for trees play a crucial role in making the component run. Two interfaces are particularly important: TreeModel, which describes how to work with tree data, and TreeSelectionModel, which describes how to select nodes.

17.2.1 The TreeModel Interface

To get started, you need a tree model. The TreeModel interface is the starting point for your model. You don't have to start from scratch; there is a default implementation (DefaultTreeModel) that you can subclass or just look at for ideas. (We'll look at this class later in the chapter.)

17.2.1.1 Property

The TreeModel has one root property, listed in Table 17-1. This read-only property designates the root of a tree: by definition, the node that has no parent. All other nodes in your tree are descendants of this node.

Table 17-1. TreeModel property

Property

Data type

get

is

set

Default value

root

Object

·

     

17.2.1.2 Events

The tree model uses the TreeModelEvent class defined in the javax.swing.event package. A TreeModelEvent indicates that the tree changed: one or more nodes were added, modified, or deleted. You will find a more detailed discussion in Section 17.6, later in this chapter.

public void addTreeModelListener(TreeModelListener l)
public void removeTreeModelListener(TreeModelListener l)

Add or remove listeners interested in receiving tree model events.

17.2.1.3 Miscellaneous methods

Several miscellaneous methods are defined in this model for querying the tree and its structure. Although the actual data structures are defined by the classes that implement the model, the model works as if every node maintains an array of its children, which in turn can be identified by an index into the array.

public Object getChild(Object parent, int index)

Given a parent object, return the child node at the given index . In many cases, if you specify an invalid index or try to get a child from a leaf, you should receive an ArrayIndexOutOfBoundsException. Of course, this is user-definable. It would also be possible to create a model that simply returned a null or default child.

public int getChildCount(Object parent)

Given a parent node, return the number of children this node has. Leaves return a value of 0.

public int getIndexOfChild(Object parent, Object child)

Given a parent node and an object, return the index of the child in the parent's children array. If the child object is not a child of this parent, it returns -1.

public boolean isLeaf(Object node)

Check to see if the given node is a leaf. By definition, a leaf has no children. This method does not distinguish between a "real" leaf (a node that should never have a child) and nodes that simply do not have a child at the time you query them. If you need to distinguish between these types of nodes, look at the allowsChildren property of the TreeNode class.

public void valueForPathChanged(TreePath path, Object newValue)

Notify the tree of a change to the user-defined object contained in the node path points to.

17.2.2 The DefaultTreeModel Class

The DefaultTreeModel class puts together a basic tree model using TreeNode objects. (We'll look at TreeNode in the next section.) This DefaultTreeModel is the basis for the first example we built. It implements the TreeModel interface and adds a few new methods. If you're in a mood to create your own tree, this is a great place to start. Each node's data is really just an Object reference, pointing to just about anything you could want to represent. (Base types, of course, must be wrapped up in objects.) You can usually get away with building on a DefaultTreeModel, unless you need to impose a specific structure, such as limiting the number of children a node can have. Normally, the children of a node are just kept in a vector, so no limits exist.

One other note about tree models: if you plan to allow multiple threads to access your model, you need to find a synchronization point. A conservative approach to thread safety dictates that you lock on the root node of the tree. Less conservative approaches require a bit of searching to make sure that you are not locking on a descendant of a node that is already locked. (If you did, it's possible the other thread would delete one of your ancestors, and thus delete you.)

17.2.2.1 Properties

The DefaultTreeModel class contains the properties listed in Table 17-2. It provides a root property (inherited from TreeModel) and a boolean property (asksAllowsChildren) to determine whether the tree model asks a node if it can accept children before inserting them. This property allows you to write nodes that can reject the addition of a child at runtime.

Table 17-2. DefaultTreeModel properties

Property

Data type

get

is

set

Default value

asksAllowsChildren*

boolean

   

·

false

rooto

Object

·

 

·

null

treeModelListeners1.4

TreeModelListener[]

·

   

null

1.4since 1.4, ooverridden

The read method for the asksAllowsChildren property is asksAllowsChildren().

17.2.2.2 Events

The DefaultTreeModel fires a TreeModelEvent whenever the tree is changed. It implements the standard methods for registering listeners, plus a number of convenience methods for firing different versions of the TreeModelEvent.

public void addTreeModelListener(TreeModelListener l)
public void removeTreeModelListener(TreeModelListener l)

Add or remove listeners interested in receiving tree model events.

protected void fireTreeNodesChanged(Object source, Object path[], int childIndices[], Object children[])
protected void fireTreeNodesInserted(Object source, Object path[], int childIndices[], Object children[])
protected void fireTreeNodesRemoved(Object source, Object path[], int childIndices[], Object children[])
protected void fireTreeStructureChanged(Object source, Object path[], int childIndices[], Object children[])

Send a TreeModelEvent to the appropriate method of all registered TreeModelListeners. The main difference between fireTreeNodesChanged( ) and fireTreeStructureChanged( ) is that the latter represents a significant change to the overall structure of the tree, while the former is typically used for minor changes, such as notifying listeners that the user data stored in a particular set of nodes has changed, or that the visual representation of those nodes has changed. These methods should be called from one of the public node change methods below. All these methods assume an ascending order for the child indices.

public void nodeChanged(TreeNode node)

Use this method any time you update a node to notify the model that the representation of this node may have changed. This is used most often to indicate that the user data in a node was edited.

public void nodesChanged(TreeNode node, int childIndices[])

Similar to nodeChanged( ). Use this method any time you update the children of a node and need to notify the model that the representation of those children may have changed.

public void nodesWereInserted(TreeNode node, int childIndices[])

This method indicates that you have added children to a node. The indices of the children you added must be sorted in ascending order.

public void nodesWereRemoved(TreeNode node, int childIndices[])

This method indicates that you have removed children from a node. The indices of the children you removed must be sorted in ascending order.

public void nodeStructureChanged(TreeNode node)

If you have completely rearranged the children (and possibly grandchildren and great grandchildren, etc.) of the node, call this method. It in turn calls the fireTreeStructureChanged( ) method.

public void valueForPathChanged(TreePath path, Object newValue)

Use this method to set the value of the user-defined object stored in this node. This method calls nodesChanged( ) for you. If you have subclassed DefaultTreeModel to create your own tree model, and that model uses specific user-defined objects in its nodes, override this method to make sure that the appropriate type of object is put into the node. For example, if your tree nodes store base types, manually extract the base value out of the wrapper object passed to this method.

17.2.2.3 Constructors
public DefaultTreeModel(TreeNode root)
public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren)

Create a new tree model implemented with TreeNode objects for the nodes. These constructors set up the given node as the root of the tree. If you specify true for the asksAllowsChildren argument of the second constructor, the model checks the node to see if it allows children before deciding whether the node is a leaf.

17.2.2.4 Miscellaneous methods

The DefaultTreeModel contains several methods to help you query and update the contents of your tree. These methods should help you add and remove nodes, as well as update the tree whenever you make other changes (such as changing user data).

public TreeNode[] getPathToRoot(TreeNode node)
protected TreeNode[] getPathToRoot(TreeNode node, int depth)

Return an array of TreeNode objects from the root down to the specified node. (The protected version uses depth as a marker to aid in its recursive discovery of the path. Normally, you use the public method.)

public void insertNodeInto(MutableTreeNode child, MutableTreeNode parent, int index)

Attach the child node to the parent node at the given index. If the index is larger than the child count (meaning more than just "the next possible index"), an ArrayIndexOutOfBoundsException is thrown.

public void reload( )
public void reload(TreeNode node)

Refresh the tree, presumably after it is modified in some fashion. The first method reloads the tree from the root down while the second call starts with the specified node. These methods are very useful when you are dynamically modifying the tree. For example, deleting a node does not immediately update JTree's display; call reload( ) on the parent of the deleted node to refresh the tree.

public void removeNodeFromParent(MutableTreeNode node)

Remove a node from its parent. The node is not destroyed but is placed in an array for use with nodesWereRemoved( ), which generates the appropriate model event for you.

17.2.3 Working with Tree Models

The TestTree example earlier in the chapter shows the DefaultTreeModel in action. However, if you want to build a more tailored tree, you can extend DefaultTreeModel and control things like the addition and removal of nodes. (Of course, if you want to build a tree model out of something other than TreeNode objects, you need to build your own class and implement the TreeModel interface. You could even have the model generate the tree dynamically.)

As an example, we build a model that sorts incoming children using DefaultMutableTreeNode for the nodes. This type of model works for mailbox managers and other places where the user can create folders and subfolders any old time. (Trees that support Drag and Drop insertions would definitely benefit.) Figure 17-5 shows an example tree that uses the SortTreeModel to load a hierarchy of files and folders. This particular example supplies a case-insensitive comparator so that the filenames don't arrange themselves in that annoying uppercase-before-lowercase thing.

Figure 17-5. A case-insensitive sorting tree
figs/swng2.1705.gif

We extend DefaultTreeModel and provide a new method for adding children to the model that does not require an index. We then calculate the proper (comparator-determined) index and insert the node using the super.insertNodeInto( ) call.

Here is the SortTreeModel class:

// SortTreeModel.java // This class is similar to the DefaultTreeModel, but it keeps // a node's children in alphabetical order. import javax.swing.tree.*; import java.util.Comparator; public class SortTreeModel extends DefaultTreeModel {   private Comparator comparator;   public SortTreeModel(TreeNode node, Comparator c) {     super(node);     comparator = c;   }   public SortTreeModel(TreeNode node, boolean asksAllowsChildren, Comparator c) {     super(node, asksAllowsChildren);     comparator = c;   }   public void insertNodeInto(MutableTreeNode child, MutableTreeNode parent) {     int index = findIndexFor(child, parent);     super.insertNodeInto(child, parent, index);   }   public void insertNodeInto(MutableTreeNode child, MutableTreeNode par, int i) {     // The index is useless in this model, so just ignore it.     insertNodeInto(child, par);   }   // Perform a recursive binary search on the children to find the right   // insertion point for the next node.   private int findIndexFor(MutableTreeNode child, MutableTreeNode parent) {     int cc = parent.getChildCount( );     if (cc == 0) {       return 0;     }     if (cc == 1) {       return comparator.compare(child, parent.getChildAt(0)) <= 0 ? 0 : 1;     }     return findIndexFor(child, parent, 0, cc - 1);  // First and last index   }   private int findIndexFor(MutableTreeNode child, MutableTreeNode parent,                             int i1, int i2) {     if (i1 == i2) {       return comparator.compare(child, parent.getChildAt(i1)) <= 0 ? i1 : i1 + 1;     }     int half = (i1 + i2) / 2;     if (comparator.compare(child, parent.getChildAt(half)) <= 0) {       return findIndexFor(child, parent, i1, half);     }     return findIndexFor(child, parent, half + 1, i2);   } }

If you want to see a model built from scratch, check out the algebraic expression tree model (ExpressionTreeModel.java) from our online examples. It stores expressions, as a computer language parser might view and evaluate such things. The ExprTree1 class starts the demonstration.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net