Home > Articles > Programming > Java

This chapter is from the book

This chapter is from the book

The Cell Renderer

Finally, let's look at the cell renderer. This needs to be customized because it has to display a specific icon for each node that has one and, conversely, it should display the standard icon if it comes across a node that does not have a special icon—that is, a node whose user object is not an instance of a class that implements the NodeWithIcon interface. In an earlier example, you saw how to change the icons that the tree uses for leaf nodes and for open and closed branch nodes by setting properties of the tree's DefaultTree-CellRenderer. However, these changes affect every icon in the tree. In this example, we need to be able to render a different icon for each leaf node. In order to do this, you need a custom renderer. Listing 10–6 showed how to create a renderer that uses a different color for the text of leaf and branch nodes. By expanding this example a little, it would be simple to have it use a different icon for each node as well - all that is necessary is to override the getLeafIcon method to return an icon that is suitable for the node being rendered. However, the approach taken in Listing 10–6 limits you to look-and-feel classes whose cell renderers are instances of DefaultTreeCellRen-derer or are subclasses of DefaultTreeCellRenderer that don't add any functionality to it that can't be lost. In many cases, of course, this is an acceptable constraint, but there is a technique that can be used to avoid being concerned about how the original renderer is implement. The code shown in Listing 10–8 shows how to create a renderer that will work in tandem with any installed renderer, as long as that renderer returns a JLabel from its getTreeCellRendererComponent method.

The new renderer works by obtaining a reference to the renderer that's installed in the tree when it is created, installing a reference to itself in its place and then calling the original renderer's getTreeCellRendererCom-ponent method at the start of its own. This allows the original renderer to create the component in its usual format, while allowing the new renderer to add its own customizations. If the component that the original renderer created is a JLabel and our node has an icon of its own, the only thing left to do is get the node's icon and install it in the original renderer's JLabel. There is no need to be concerned about how the label is organized, because this is a simple trade of one icon for another. You'll find the implementation details in Listing 10-8.

There is one small point in this renderer that is worth taking note of. The getTreeCellRendererComponent method replaces the icon in the JLa-bel created by the original renderer like this:

if (leaf && c instanceof JLabel) {
  JLabel l = (JLabel)c;
  Icon icon = getLeafIcon();
  if (icon != null) {
    l.setIcon(icon);
  }
} 

In fact, we know that the Icon to be used is readily available to the getTreeCellRendererComponent method, because it is part of the node that is being rendered, assuming that the node's user object implements the NodeWithIcon interface (and if it doesn't, the original icon should be left in place). However, instead of extracting the icon directly, the task is delegated to the getLeafIcon method. The reason for this is actually to do with editing. As you know, in order to keep the correct icon displayed during an editing session, the DefaultTreeCellEditor constructor is given a reference to the tree's renderer. When the editor is activated, the renderer's getLeaf-Icon method is called to get the correct icon. For this reason, our renderer is obliged to supply a getLeafIcon method that gets the appropriate icon for whichever node is being edited and, since we had to implement this method, we chose to use it in the getTreeCellRendererComponent method as well. Here is how it is implemented:

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; 
} 

There is a small subtlety in this code. The icon has to be obtained from the node being rendered (or edited in the case of an editing session), but the DefaultTreeCellRenderer getLeafIcon method does not receive a reference to this node. As with our custom editor implementation that was shown in "Controlling Editability in the Tree Cell Editor, " we solve this problem by storing a reference to the last node that was rendered in the getTreeCellRendererComponent method and then use it in getLeafI-con. This works both during normal rendering and when an editing session is in progress.

The rest of the code for this example just creates an instance of the tree with suitable data and displays it. Figure 10–15 shows what it looks like with one hardware item selected. If you run this example by typing the command:

Java JFCBook.Chapter10.CustomIcons

Figure 10–15 A tree node with a custom icon.

you can try out editing the leaf node by triple-clicking on it. You'll get a combo box with two selections in it. Notice how the text and the image both change when you change the selection. Notice also that the nonleaf nodes are not editable.

  • + Share This
  • 🔖 Save To Your Account