JavaSwingJTree and TreeModel: DefaultTreeModel


DefaultTreeModel

TreeNodes

Equality

As they are usually contained in a TreeModel, tree nodes must be immutable and unique (i.e. just take Object's implementation of equals() and hashCode()).

One could define other equivalence relations on tree nodes (or the subtrees they represent), for example by recursively comparing the children (and maybe their user objects if they exist and are accessible), or by comparing the whole tree structure, including the ancestors. These may exist in addition, but cannot be used for equals() if the tree nodes should be in a tree model. Besides, both hashCode() and equals() then would have a lot of overhead.

DefaultMutableTreeNode doesn't override equals() or hashCode(), as is sensible.

User objects

MutableTreeNodes have a "user object" (whatever that is supposed to mean). Often, this is the really interesting thing about the node, the TreeNodes themselves only forming the tree structure. There are some problems with that:

Changes

For "free" tree nodes (without a TreeModel), there are almost no requirements about when it changes (It is sensible to assume that it doesn't do when only accessor methods are called), with a TreeModel, either the TreeModel must be notified (that requires setting up a reference to a TreeModel somewhere), or the changes must have been caused by the TreeModel (so that it knows to fire events afterwards).

Lazy creation of children

A TreeNode may create its children only on demand. If the node is really part of a TreeModel, it may either be done in the TreeModel or in the node. Putting it in the node has the advantage that code that inspects the tree structure (if that ever happens) directly will cause the creation.

DefaultMutableTreeNode adds so many methods that would have to be overridden to first create the children that it is almost impossible to do.

DefaultTreeModel

Implicit removals

When adding children to a DefaultMutableTreeNode, they are implicitly removed from the old parent. This "feature" (I would rather have an Exception instead of implicit magic) cannot be used when the concerned nodes are contained in the TreeModel. Even if insertNodeInto() is used to automatically fire an event of the insertion, DefaultTreeModel doesn't (and can't in general, since the old parent may be part in a completely different tree structure or TreeModel) fire an event for the removal. Always first make sure the nodes you add don't have a parent.

Tree node editing

DefaultTreeModel sets the new value given to valueForPathChanged() when editing has finished as the user object of the path's node, so the old user object is replaced by (typically) a String. If you only want to change a property of your user object or react in still a different way, do something similar to this:

public void valueForPathChanged(TreePath path, Object newValue)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();

((Whatever)node.getUserObject()).setProperty(newValue);

nodeChanged(node);
}

If the user object should be replaced by a new one, but not by a String, one could also replace the TreeCellEditor not to return a String, but already the new Object from getCellEditorValue().

Finding things

Finding a TreePath for a node

If you already have a reference to a node and want to find (unique) path in the TreeModel for it, use:

new TreePath(data.getPathToRoot(node));

Care must be exercised that the node really is in the tree structure: the implementation of DefaultTreeModel doesn't notice that an invalid node has been given (although it could without effort).

Note: If the root of the DefaultTreeModel and the root of the tree structure aren't the same (i.e. the TreeModel only displays a subtree of the whole structure), then new TreePath(node.getPath()) (in DefaultMutableTreeNode) would be wrong: it returns the path to the tree structure root, which isn't part of the TreeModel anymore. So better always use getPathToRoot().

Finding a node for a user object

If you only know the user object of a node, you have to search for it in the tree structure.

There may be cases where the user object isn't unique, then in general you can only search until you find one occurence, or collect all.

If you use DefaultMutableTreeNode, you can use its tree walk methods, for example::

DefaultTreeModel data = ...;

DefaultMutableTreeNode root = (DefaultMutableTreeNode)data.getRoot();

DefaultMutableTreeNode node = null;

if (root != null)
for (Enumeration e = root.getBreadthFirstEnumeration(); e.hasMoreElements();)
{
DefaultTreeModeNode current = (DefaultMutableTreeNode)e.nextElement();

if (target.equals(current.getUserObject()))
{
node = current;
break;
}
}

if (node != null)
{
// found
}

Without DefaultMutableTreeNode, it cannot be done in a generic way, since there is no way to access the user object at all.

If you really need a path, you can then use new TreePath(data.getPathToRoot(node)); as above.

Finding the index of a user object

If you know the user object is contained in a child node of a certain parent node, you can search only that node's children.

public int indexOfUserObject(DefaultMutableTreeNode parent, Object userObject)
{
for (Enumeration e = parent.children(); e.hasMoreElements();)
{
if (userObject.equals(((DefaultMutableTreeNode)e.nextElement()).getUserObject()))
return index;

index++;
}

return -1;
}

(C) 2001-2009 Christian Kaufhold (swing@chka.de)