Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 17. (Advanced topics) Trees. Easy for reading, Click here!

Custom Search
Swing Chapter 17. (Advanced topics) Trees. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 7/7 



Previous Page Previous Page (6/7)
Subpages: 1. JTree 
2.
Basic JTree example 
3.
Directories tree: part I - Dynamic node retrieval 
4. Directories tree: part II - Popup menus and programmatic navigation 
5. Directories tree: part III - Tooltips 
6. JTree and XML documents 
7. Custom editors and renderers 

17.7  Custom editors and renderers

In this section we'll construct a simple family tree application. We will show how to use a custom cell editor for name entry, as well as a custom cell renderer which displays an icon corresponding to a node's data rather than its state. This example allows dynamic node insertion, and each node can have no more than two children.

Our representation of an ancestor tree is structured differently than how we normally think of structuring trees, even though, technically speaking, both methods are equivalent. Our root tree node represents a child, and child tree nodes represent parents, grandparents, etc., of that child. So a parent node in this JTree actually corresponds to a child in the family ancestry. This illustrates that JTree is flexible enough to allow adaptation to any type of heirarchical data set, including a dynamically changing one (as we also saw in our file directories tree examples above).

Figure 17.7 JTree with custom editor and cell renderer enforcing nodes with two children.

<<file figure17-7.gif>>

The Code: AncestorTree.java

see \Chapter17\6

import java.awt.*;

import java.awt.event.*;

import java.util.*;

import javax.swing.*;

import javax.swing.border.*;

import javax.swing.event.*;

import javax.swing.tree.*;

public class AncestorTree extends JFrame

{

  public static ImageIcon ICON_SELF =

    new ImageIcon("myself.gif");

  public static ImageIcon ICON_MALE =

    new ImageIcon("male.gif");

  public static ImageIcon ICON_FEMALE =

    new ImageIcon("female.gif");

  protected JTree  m_tree;

  protected DefaultTreeModel m_model;

  protected IconCellRenderer m_renderer;

  protected IconCellEditor m_editor;

  public AncestorTree() {

    super("Ancestor Tree");

    setSize(500, 400);

    DefaultMutableTreeNode top = new DefaultMutableTreeNode(

      new IconData(ICON_SELF, "Myself"));

    addAncestors(top);

    m_model = new DefaultTreeModel(top);

    m_tree = new JTree(m_model);

    m_tree.getSelectionModel().setSelectionMode(

      TreeSelectionModel.SINGLE_TREE_SELECTION);

    m_tree.setShowsRootHandles(true);

    m_tree.setEditable(true);

    m_renderer = new IconCellRenderer();

    m_tree.setCellRenderer(m_renderer);

    m_editor = new IconCellEditor(m_tree);

    m_tree.setCellEditor(m_editor);

    m_tree.setInvokesStopCellEditing(true);

    m_tree.addMouseListener(new TreeExpander());

    JScrollPane s = new JScrollPane();

    s.getViewport().add(m_tree);

    getContentPane().add(s, BorderLayout.CENTER);

    WindowListener wndCloser = new WindowAdapter() {

      public void windowClosing(WindowEvent e) {

        System.exit(0);

      }

    };

    addWindowListener(wndCloser);

    setVisible(true);

  }

  public boolean addAncestors(DefaultMutableTreeNode node) {

    if (node.getChildCount() > 0)

      return false;

    Object obj = node.getUserObject();

    if (obj == null)

      return false;

    node.add(new DefaultMutableTreeNode( new IconData(

      ICON_MALE, "Father of: "+obj.toString()) ));

    node.add(new DefaultMutableTreeNode( new IconData(

      ICON_FEMALE, "Mother of: "+obj.toString()) ));

    return true;

  }

  public static void main(String argv[]) { new AncestorTree(); }

  class TreeExpander extends MouseAdapter

  {

    public void mouseClicked(MouseEvent e) {

      if (e.getClickCount() == 2) {

        TreePath selPath = m_tree.getPathForLocation(

          e.getX(), e.getY());

        if (selPath == null)

          return;

        DefaultMutableTreeNode node =

          (DefaultMutableTreeNode)(selPath.

             getLastPathComponent());

        if (node!=null && addAncestors(node)) {

          m_tree.expandPath(selPath);

          m_tree.repaint();

        }

      }

    }

  }

}

// Classes IconCellRenderer and IconData are

// unchanged from previous examples, and are

// not listed here to conserve space.

class IconCellEditor extends JLabel

 implements TreeCellEditor, ActionListener

{

  protected JTree m_tree = null;

  protected JTextField m_editor = null;

  protected IconData m_item = null;

  protected int m_lastRow = -1;

  protected long m_lastClick = 0;

  protected Vector m_listeners = null;

  public IconCellEditor(JTree tree) {

    super();

    m_tree = tree;

    m_listeners = new Vector();

  }

  public Component getTreeCellEditorComponent(JTree tree,

   Object value, boolean isSelected, boolean expanded,

   boolean leaf, int row)

  {

    if (value instanceof DefaultMutableTreeNode) {

      DefaultMutableTreeNode node =

        (DefaultMutableTreeNode)value;

      Object obj = node.getUserObject();

      if (obj instanceof IconData) {

        IconData idata = (IconData)obj;

        m_item = idata;

        // Reserve some more space...

        setText(idata.toString()+"     ");

        setIcon(idata.m_icon);

        setFont(tree.getFont());

        return this;

      }

    }

    // We don't support other objects...

    return null;

  }

  public Object getCellEditorValue() {

    if (m_item != null && m_editor != null)

      m_item.m_data = m_editor.getText();

    return m_item;

  }

  public boolean isCellEditable(EventObject evt) {

    if (evt instanceof MouseEvent) {

      MouseEvent mEvt = (MouseEvent)evt;

      if (mEvt.getClickCount() == 1) {

        int row = m_tree.getRowForLocation(mEvt.getX(), mEvt.getY());

        if (row != m_lastRow) {

          m_lastRow = row;

          m_lastClick = System.currentTimeMillis();

          return false;

        }

        else if (System.currentTimeMillis()-m_lastClick > 1000)

        {

          m_lastRow = -1;

          m_lastClick = 0;

          prepareEditor();

          mEvt.consume();

          return true;

        }

        else

          return false;

      }

    }

    return false;

  }

  protected void prepareEditor() {

    if (m_item == null)

      return;

    String str = m_item.toString();

    m_editor = new JTextField(str);

    m_editor.addActionListener(this);

    m_editor.selectAll();

    m_editor.setFont(m_tree.getFont());

    add(m_editor);

    revalidate();

    TreePath path = m_tree.getPathForRow(m_lastRow);

    m_tree.startEditingAtPath(path);

  }

  protected void removeEditor() {

    if (m_editor != null) {

      remove(m_editor);

      m_editor.setVisible(false);

      m_editor = null;

      m_item = null;

    }

  }

  public void doLayout() {

    super.doLayout();

    if (m_editor != null) {

      int offset = getIconTextGap();

      if (getIcon() != null)

        offset += getIcon().getIconWidth();

      Dimension cSize = getSize();

      m_editor.setBounds(offset, 0, cSize.width - offset,

        cSize.height);

    }

  }

  public boolean shouldSelectCell(EventObject evt) { return true; }

  public boolean stopCellEditing() {

    if (m_item != null)

      m_item.m_data = m_editor.getText();

    ChangeEvent e = new ChangeEvent(this);

    for (int k=0; k<m_listeners.size(); k++) {

      CellEditorListener l = (CellEditorListener)m_listeners.

        elementAt(k);

      l.editingStopped(e);

    }

    removeEditor();

    return true;

  }

  public void cancelCellEditing() {

    ChangeEvent e = new ChangeEvent(this);

    for (int k=0; k<m_listeners.size(); k++) {

      CellEditorListener l = (CellEditorListener)m_listeners.

        elementAt(k);

      l.editingCanceled(e);

    }

    removeEditor();

  }

  public void addCellEditorListener(CellEditorListener l) {

    m_listeners.addElement(l);

  }

  public void removeCellEditorListener(CellEditorListener l) {

    m_listeners.removeElement(l);

  }

  public void actionPerformed(ActionEvent e) {

    stopCellEditing();

    m_tree.stopEditing();

  }

}

Understanding the Code

Class AncestorTree

Class AncestorTree declares and creates three static images representing male and female ancestors, and a root (representing a child whose ancestry is being represented).

Instance variables:

JTree  m_tree: ancestors tree component.

DefaultTreeModel m_model: ancestor tree data model.

IconCellRenderer m_renderer: ancestor tree cell renderer.

IconCellEditor m_editor: ancestor tree cell editor.

The AncestorTree constructor is similar to the Tree1 constructor from section 17.2. However, there are some important differences:

addAncestors() method (see below) is called to create initial child nodes.

The editable property is set to true.

Instances of our custom IconCellRenderer and IconCellEditor classes are set as the renderer and editor for m_tree, respectively.

An instance of our custom TreeExpander class is attached as a MouseListener to our m_tree.

The addAncestors() method adds male and female ancestors to a given node as child nodes (representing parents in an ancestry), if this hasn't been done already. Instances of IconData (which hold  a combination of icons and text, see previous examples) are added as user objects for a newly created node. The initial text of each IconData object is assigned as "Father of: <node text>" and " Mother of: <node text>", and  appropriate icons are used to distinguish between women and men.

Class AncestorTree.TreeExpander

The TreeExpander inner class extends MouseAdapter and is used to insert ancestor nodes when a double click occurs on a node. The most significant aspect of this listener is the call to the JTree.getPathForLocation() method, which retrieves the currently selected tree path and allows us to determine the selected node. The addAncestors() method is then called on that node, and our tree is repainted to show the newly added nodes (if any).

Class IconCellRenderer and class IconData

Class IconCellRenderer, as well as class IconData, have recieved no changes from section 17.3. Refer back to this section for more information about their inner workings.

Class IconCellEditor

This class implements the TreeCellEditor and ActionListener interfaces, and creates a JTextField for editing a node's text in-place. This editor is designed in such a way that a cell icon remains in unchanged and in the same location whether in editing mode or not. This explains why IconCellEditor extends JLabel, and not JTextField as we might expect. The underlying JLabel component is used to render the icon and reserve any ecessary space. The JTextField component is created dynamically and placed above the JLabel's text portion to perform the actual editing. Note that the JLabel serves as a Container for the JTextField component (recall that all Swing components extend the java.awt.Container class).

Note: Swing's default editor DefaultTreeCellEditor is used similarly, but in a more complex manner. It takes a DefaultTreeCellRenderer as parameter and uses it to determine the size of a node's icon. Then it uses a custom inner class as a container which renders the icon and positions the text field. For more details see DefaultTreeCellEditor.java.

Instance variables:

JTree m_tree: reference to the parent tree component (must be passed to constructor).

JTextField m_editor: editing component.

IconData m_item: data object to be edited (we limit ourselves to IconData instances).

int m_lastRow: tree row where the most recent mouse click occured.

long m_lastClick: time (in ms) that the last mouse click occurred.

Vector m_listeners: collection of CellEditorListeners, which we must manage according to the TreeCellEditor interface.

The getTreeCellEditorComponent() method will be called to initialize a new data object in the editor before we begin editing a node. (Note that the user object from the selected node must be an instance of IconData, or editing is not allowed.) The icon and text from the IconData object are assigned using inherited JLabel functionality. A few spaces are intentionally appended to the end of the text to provide some white space between the icon and the editing area (a very simply way to accomplish this).

The getCellEditorValue() method returns the current value of the editor as an Object. In our case we return m_item, adjusting its m_data to m_editor's text:

      if (m_item != null && m_editor != null)

        m_item.m_data = m_editor.getText();

      return m_item;

Method isCellEditable() is called to determine whether an editor should enter editing mode. It takes an EventObject as parameter. In this way, we can use any user activity resulting in an event as a signal for editing, e.g. double mouse click, specific key press combination, etc. We've implemented this method to start editing mode when two single mouse clicks occur on a cell separated by no less than 1 second (1000 ms). To do this we first filter only single mouse click events:

      if (evt instanceof MouseEvent) {

        MouseEvent mEvt = (MouseEvent)evt;

        if (mEvt.getClickCount() == 1) {

For these events we determine the tree's row that was clicked and store it in our m_lastRow instance variable. The current system time is also saved in the m_lastClick instance variable. If another click occurs on the same cell after 1000 ms, our custom prepareEditor() method is called to prepare the editor for editing, and returns true. Otherwise false is returned.

Method shouldSelectCell()always returns true to indicate that a cell's content should be selected at the beginning of editing.

Method stopCellEditing() is called to stop editing and store the result of the edit. We simply change m_data in the m_item object to m_editor's text.

      if (m_item != null)

        m_item.m_data = m_editor.getText();

The m_item object also has a reference to our tree's model, so it will affect our tree directly. Thus all registered CellEditorListeners are notified in turn by calling their editingStopped() method. Finally the editing process is terminated by calling our custom removeEditor() method.

The cancelCellEditing() method is called to stop editing without storing the result of the edit. Similar to stopCellEditing(), all registered CellEditorListeners are notified in turn by calling their editingCanceled(), and the editing process is terminated by calling removeEditor().

Two methods, addCellEditorListener() and removeCellEditorListener(), add and remove listeners to/from our m_listeners vector.

The prepareEditor() method actually starts the editing process. It creates a JTextField component and sets its initial text to that of the m_item object:

      m_editor = new JTextField(str);

      m_editor.addActionListener(this);

      m_editor.selectAll();

      m_editor.setFont(m_tree.getFont());

      add(m_editor);

      revalidate();

An ActionListener is added to the text field to enable the ability to stop editing when the user presses the "Enter" key (recall that this class implements ActionListener, so we provide a this reference for the listener). The most important aspect of this method is the fact that a JTextField is added to our base component, and the overridden doLayout() method is invoked indirectly (through revalidate()) to assign the correct size and position to our editor component. Finally, the base tree is notified by calling our startEditingAtPath() method, to allow editing.

The removeEditor() method is called to quit the editing process. It removes the editing component from the container, hides and destroys it (dereferences it to allow garbage collection):

          remove(m_editor);

          m_editor.setVisible(false);

          m_editor = null;

          m_item = null;

Method doLayout() overrides Component.doLayout() to set the correct size and location for the editing component.

Method actionPerformed() will be called when "Enter" is pressed during editing. It directly calls our stopCellEditing() implementation, and notifies our tree by calling stopEditing().

Running the Code

Perform a single click on a tree cell, wait about a second, then click it again to enter editing mode. Note that there is no limit to how far back we can go with this ancestor tree, and all nodes can have either no children, or two children (representing the parents of the individual represented by that node). Try creating your own ancestor tree as far back as you can go (if you end with monkey or gorilla please contact us). Figure 17.7 shows the ancestor tree of Prince William of Wales, the oldest son of the heir of the British monarchy.

UI Guideline : Family Trees and Organisation Charts

The family tree given here is used as an example only. There is still considerable debate in the UI Design field as to whether Tree component is appropriate for displaying and manipulating such data. Generally, Family Trees or Organisation Charts are displayed using a top-down (horizontal orientation), evenly distributed graph. Therefore, the Tree component view with its Left-Right (vertical orientation) is alien for this type of data.

If your User community is particularly technical then you should have no difficulties, however, consider carefully before selecting tree component for a wider user group, in this instance.

You may also like to consider that such a tree component could be used a prototype or "proof of concept". You could later replace the Tree component with an OrganisationChart component (for example) which re-uses the same TableModel interface. Thus the actual domain data and model classes would not need to be changed. The ability to do this, demonstrates the power of the Swing MVC architecture.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy