- The Tree Control
- Tree Appearance
- The TreeNode Interface
- The MutableTreeNode Interface
- The DefaultMutableTreeNode Class
- The TreePath Class
- What is a Leaf?
- Tree Expansion and Traversal
- Expanding and Collapsing Nodes under Program Control
- Tree Expansion Events
- Making Nodes Visible
- Controlling Node Expansion and Collapse
- Tree Model Events
- Implementation Plan for the File System Control
- File System Tree Control Implementation
- Using the File System Tree Control
- Custom Tree Rendering and Editing
- Customizing the Default Tree Cell Renderer
- ToolTips and Renderers
- Custom Cell Editors
- Controlling Which Nodes Can Be Edited
- Controlling Editability by Subclassing JTree
- Programmatic Control of Editors
- Editing Trees with Custom User Objects
- The valueForPathChanged Method
- The Tree Implementation
- The Cell Editor
- The Cell Renderer
- Summary
The valueForPathChanged Method
The only other method of the data model that still needs to be described is the one that this section opened withvalueForPathChanged. This method is called with a TreePath for the node that has been edited and the new value which will have come from the combo box and which is therefore known to be a String, because the combo box was created using the get-NamesAsCombo method, which populates it with Strings. What value-ForPathChanged needs to do is find the correct user object corresponding to the String returned from the editor and install it in the node. Finding the user object is easythe data model has a hashtable that maps from the name to the object itself, so all that is necessary is to invoke the getObjectFor-Name method, which accesses the hashtable to locate the object. This method only returns an appropriate answer if the String returned by the combo box was the key for one of the objects that are in the hashtable. Given the way that this example has been written, this will always be true, but if you take the code here and use it in another application which allows the combo box to be editable (probably a mistake!), the user may type a meaningless value and you won't find a corresponding object in the hashtable. To defend against this possibility, if this happens, getObjectForName will return null, and the exact value that the user typed is stored as the user object. Whether or not this is of any use depends on the application: It does at least stop the program getting a NullPointerException.
Having got the correct object from the getObjectForName method, the next step is to find the node to install it in. You already know how to do thisyou just apply getLastPathComponent to the TreePath passed to val-ueForPathChanged to find the node and then call setUserObject to install the user object. Finally, because the content of the model has changed, the nodeChanged method must be called to have the data model send events to registered TreeModelListeners. This will cause the tree's visual state to be updated.
That takes care of the data model. However, this example also needs three other things:
A JTreeto display the model.
An editor that provides a combo box with the legal hardware types installed.
A renderer that will display the hardware object in each of the tree's leaf nodes.
We'll cover each of these items separately. For reference, the complete implementation, apart from the data model, which was covered earlier, is shown in Listing 108.
Listing 108 Rendering and editing nodes with custom icons
package JFCBook.Chapter10; import javax.swing.*; import javax.swing.tree.*; import javax.swing.event.*; import java.util.*; import java.awt.Component; import java.awt.event.*; public class CustomIcons { public static void main(String[] args) { JFrame f = new JFrame("Custom Icons"); // Create the model with just the root node DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Apollo"); CustomTreeModel m = new CustomTreeModel(rootNode); JTree t = new JTree(m) { public boolean isPathEditable(TreePath path) { // Only allow editing of leaf nodes return isEditable() && getModel().isLeaf(path.getLastPathComponent()); } }; t.putClientProperty("JTree.lineStyle", "Angled"); t.setEditable(true); t.setRowHeight(0);// Row height is not fixed // Define a replacement renderer final TreeCellRenderer oldRenderer = t.getCellRenderer(); DefaultTreeCellRenderer r = new DefaultTreeCellRenderer() { private Object lastValue; public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { // Store the value for getLeafIcon lastValue = value; // Allow the original renderer to set up the label Component c = oldRenderer.getTreeCellRendererComponent( tree, value, selected, expanded, leaf, row, hasFocus); // Now change the icon if necessary if (leaf && c instanceof JLabel) { JLabel l = (JLabel)c; Icon icon = getLeafIcon(); if (icon != null) { l.setIcon(icon); } } return c; } public Icon getLeafIcon() { Object o = ((DefaultMutableTreeNode)lastValue).getUserObject(); if (o instanceof NodeWithIcon) { return ((NodeWithIcon)o).getIcon(); } else if (oldRenderer instanceof DefaultTreeCellRenderer) { return ((DefaultTreeCellRenderer)oldRenderer). getLeafIcon(); } return null; } }; t.setCellRenderer(r); // Define a replacement editor JComboBox combo = m.getNamesAsCombo(); DefaultCellEditor comboEditor = new DefaultCellEditor(combo); DefaultTreeCellEditor editor = new DefaultTreeCellEditor(t, r, comboEditor); t.setCellEditor(editor); // Populate the model with a small amount of data DefaultMutableTreeNode hardwareNode = new DefaultMutableTreeNode("Hardware"); rootNode.add(hardwareNode); String itemName = (String)combo.getItemAt(0); DefaultMutableTreeNode itemNode = new DefaultMutableTreeNode( m.getObjectForName(itemName)); hardwareNode.add(itemNode); t.expandRow(0); t.expandRow(1); f.getContentPane().add(new JScrollPane(t)); f.setSize(300, 300); f.setVisible(true); } }