Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

Writing Advanced Applications
Chapter 6 Continued: Components and Data Models

[<<BACK] [CONTENTS] [NEXT>>]

The AuctionClient program is a simple GUI application that lets auction administrators list and browse auction items, and print auction item reports. This section describes the Project Swing application code, which uses lightweight components and the other Project Swing features shown in the bullet list.


Lightweight Components

All components in Project Swing, except JApplet, JDialog, JFrame and JWindow are lightweight components. Lightweight components, unlike their Abstract Window Toolkit (AWT) counterparts, do not depend on the local windowing toolkit.

For example, a heavyweight java.awt.Button running on the JavaTM platform for the Unix platform maps to a real Motif button. In this relationship, the Motif button is called the peer to the java.awt.Button. If you create two java.awt.Button in an application, two peers and hence two Motif Buttons are also created. The Java platform communicates with the Motif Buttons using the Java Native Interface. For each and every component added to the application, there is an additional overhead tied to the local windowing system, which is why these components are called heavyweight.

Lightweight components are termed peerless components and emulate the local window system components. A lightweight button is represented as a rectangle with a label inside that accepts mouse events. Adding more lightweight buttons means drawing more rectangles.

A lightweight component needs to be drawn on something, and an application written in the Java programming language needs to interact with the local window manager so the main application window can be closed or minimized. This is why the top-level parent components mentioned above (JFrame, JApplet, and others) are implemented as heavyweight components -- they need to be mapped to a component in the local window toolkit.

A JButton is a very simple shape to draw. For more complex components like JList or JTable, the elements or cells of the list or table are drawn by a CellRenderer object. A CellRenderer object provides flexibility because it makes it possible for any type of object to be displayed in any row or column.

For example, a JTable can use a different CellRenderer for each column. This code segment sets the second column, which is referenced as index 1, to use a CustomRenderer object to create the cells for that column.

  JTable scrollTable=new JTable(rm);
  TableColumnModel scrollColumnModel = 
		scrollTable.getColumnModel();    
  CustomRenderer custom = new CustomRenderer();
  scrollColumnModel.getColumn(1).setCellRenderer(custom);

Ordering Components

Each Project Swing applet or application needs at least one heavyweight container component (a JFrame, JWindow, JApplet, or JDialog). Each of these containers with JFrame's lightweight MDI counterpart, JInternalFrame, contains a component called a RootPane. The JRootPane manages the additional layers used in the container such as the JLayeredPane, JContentPane, GlassPane and the optional JMenuBar. It also lets all emulated (lightweight) components interact with the AWT event queue to send and receive events. Interacting with the event queue gives emulated components indirect interaction with the local window manager.

JLayeredPane

The JLayeredPane sits on top of the JRootPane, and as its name implies, controls the layers of the components contained within the boundary of the heavyweight container. The components are not added to the JLayeredPane, but to the JContentPane instead. The JLayeredPane determines the Z-ordering of the components in the JRootPane. The Z-order can be thought of as the order of overlay among various the components. If you drag-and-drop a component or request a dialog to popup, you want that component to appear in front of the others in the application window. The JLayeredPane lets you layer components.

The JLayeredPane divides the depth of the container into different bands that can be used to assign a component to a type-appropriate level. The DRAG_LAYER band, value 400, appears above all other defined component layters. The lowermost level of JLayeredpane, the DEFAULT_FRAME_LAYER band, has value -3000 and is the level of the heavyweight containers, including the MenuBar. The bands are as follows:

Value Layer Name Component Types
-3000 DEFAULT_FRAME_LAYER JMenubar
0 DEFAULT_LAYER JButton, JTable, ..
PALETTE_LAYER Floating components
such as JToolBar
MODAL_LAYER Modal Dialogs
400 DRAG_LAYER Drag-and-drop
over all layers

Within these general depth bands, components can be further arranged with another numbering system to order the components in a particular band, but this system reverses the numbering priority. For example, in a specific band such as DEFAULT_LAYER, components with a value of 0 appear in front of others in that band; whereas, components with a higher number or -1 appear behind them. The highest number in this scheme is the number of components minus 1, so one way to visualize it is a vector of components that steps through painting the components with a higher number first finishing with the one at position 0.

For example, the following code adds a JButton to the default layer and specifies that it appear in front of the other components in that same layer:

  JButton enterButton = new JButton("Enter");
  layeredPane.add(enterButton, 
		  JLayeredPane.Default_Layer, 0);
You can achieve the same effect by calling the LayeredPane.moveToFont method within a layer or using the LayeredPane.setLayer method to move to a different layer.

JContentPane

The JContentPane manages adding components to heavyweight containers. So, you have to call the getContentPane method to add a component to the ContentPane of the RootPane. By default, a ContentPane is initialized with a BorderLayout layout manager. There are two ways to change the layout manager. You can call the setLayout method like this:
  getContentPane()).setLayout(new BoxLayout())
Or you can replace the default ContentPane with your own ContentPane, such as a JPanel, like this:
  JPanel pane= new JPanel();
  pane.setLayout(new BoxLayout());
  setContentPane(pane);

GlassPane

The GlassPane is usually completely transparent and just acts as a sheet of glass in front of the components. You can implement your own GlassPane by using a component like JPanel and installing it as the GlassPane by calling the setGlassPane method. The RootPane is configured with a GlassPane that can be retrieved by calling getGlassPane.

One way to use a GlassPane is to implement a component that invisibly handles all mouse and keyboard events, effectively blocking user input until an event completes. The GlassPane can block the events, but currently the cursor will not return to its default state if you have set the cursor to be a busy cursor in the GlassPane. An additional mouse event is required for the refresh.

  MyGlassPane  glassPane = new MyGlassPane();
  setGlassPane(glassPane);       
  setGlassPane.setVisible(true); //before worker thread
  ..
  setGlassPane.setVisible(false); //after worker thread

  private class MyGlassPane extends JPanel {

    public MyGlassPane() {
      addKeyListener(new KeyAdapter() { });
      addMouseListener(new MouseAdapter() { });
      super.setCursor(
	Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }
  }                        

Data Models

Numerous model layers are combined to form the tables of the AuctionClient GUI. At a foundational level, the TableModel interface and its two implementations AbstractTableModel and DefaultTableModel provide the most basic means for storage, retrieval and modification of the underlying data.

The TableModel is responsible for defining and categorizing the data by its class. It also determines if the data can be edited and how the data is grouped into columns and rows. It is important to note, however, that while the TableModel interface is used most often in the construction of a JTable, it is not fundamentally tied to their display. Implementations could just as easily form the basis of a spreadsheet component, or even a non-GUI class that calls for the organization of data in tabular format.

The ResultsModel class is at the heart of the AuctionClient tables. It defines a dynamic data set, dictates whether class users can edit the data through its ResultsModel.isCellEditable method, and provides the update method to keep the data current. The model underlies the scrolling and fixed tables, and lets modifications to be reflected in each view.

At a higher level, and representing an intermediate layer between data and its graphical representation, is the TableColumnModel. At this level the data is grouped by column in anticipation of its ultimate display in the table. The visibility and size of these columns, their headers, and the component types of their cell renderers and editors are all managed by the TableColumnModel class.

For example, freezing the left-most columns in the AuctionClient GUI is possible because column data is easily exchanged among multiple TableColumnModel and JTable objects. This translates to the fixedTable and scrollTable objects of the AuctionClient program.

Higher still lie the various renderers, editors, and header components whose combination define the look and organization of the JTable component. This level is where the fundamental layout and display decisions of the JTable are made.

The creation of the inner classes CustomRenderer and CustomButtonRenderer within the AuctionClient application allows users of those classes to redefine the components upon which the appearance of table cells are based. Likewise, the CustomButtonEditor class takes the place of the table's default editor. In true object-oriented fashion, the default editors and renderers are easily replaced, affecting neither the data they represent nor the function of the component in which they reside.

Finally, the various component user interfaces are responsible for the ultimate appearance of the JTable. It is here the look-and-feel-specific representation of the AuctionClient tables and their data are rendered in final form to the user. The end result is that adding a Project Swing front-end to existing services requires little additional code. In fact, coding the model is one of the easier tasks in building a Project Swing application.

Table Model

The JTable class has an associated DefaultTableModel class that internally uses a Vector of vectors to store data internally. The data for each row is stored in a singl Vector object while another Vector object stores each of those rows as its constituent elements. The DefaultTableModel object can be initialized with data in several different ways. This code shows the DefaultTableModel created with a two-dimensional array and a second array representing column headings. The DefaultTableModel in turn converts the Object arrays into the appropriate Vector objects:
  Object[][] data = new Object[][]{ {"row 1 col1", 
			            "Row 1 col2" },
                        	    {"row 2 col 1", 
				    "row 2 col 2"} 
				  };
  Object[] headers = new Object[] {"first header",
			           "second header"};
  DefaultTableModel model = new DefaultTableModel(data, 
				  headers);
 
  table = new JTable(model);
  table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);          
Creating a custom table model is nearly as easy as using DefaultTableModel, and requires little additional coding. You can implement a table model by implementing a method to return the number of entries in the model, and a method to retrieve an element at a specific position in that model. For example, the JTable model can be implemented from javax.swing.table.AbstractTableModel by implementing the methods getColumnCount, getRowCount and getValueAt as shown here:
  final Object[][] data = new Object[][]{ {
			"row 1 col1",
			"row 1 col2" },
                        {"row 2 col 1",
			"row 2 col 2"} };
  final Object[] headers = new Object[] {
			"first header",
			"second header"};
 
  TableModel model = new AbstractTableModel(){
    public int getColumnCount() {
      return data[0].length;
    }
    public int getRowCount() {
      return data.length;
    }
    public String getColumnName(int col) {
      return (String)headers[col];
    }
 
    public Object getValueAt(int row,int col) {
      return data[row][col];
    }
  };
  table = new JTable(model);
  table.setAutoResizeMode(
	JTable.AUTO_RESIZE_OFF);                       
This table is read-only and its data values are already known. In fact, the data is even declared final so it can be retrieved by the inner TableModel class. This is not normally the situation when working with live data.

You can create an editable table by by adding the isCellEditable verification method, which is used by the default cell editor, and the AbstractTableModel method for setting a value at a position. Up until this change, the AbstractTableModel has been handling the repainting and resizing of the table by firing different table changed events. Because the AbtractTableModel does not know that something has occured to the table data, you need to inform it by calling the fireTableCellUpdated method. The following lines are added to the AbstractTableModel inner class to allow editing of the data:

  public void setValueAt (Object value, 
			int row, int col) {
    data[row][col] = value;
    fireTableCellUpdated (row, col);
  }

  public boolean isCellEditable(int row, 
			int col) {
    return true;
  }                                   

More Table Models

A common requirement for the display of tabular data is the inclusion of a non-scrolling column. This column provides a set of anchor data that remains stationary and visible while its neighboring columns are scrolled horizontally (and often out of view). This is particularly important in cases where row data can be identified by a unique value in the fixed column, such as a name or identification number. The next code example uses a fixed table column to display a list of the auction items.

The base table model in this example implements the AbstractTableModel class. Its update method dynamically populates the table data from a call to the database. It sends an event that the table has been updated by calling the fireTableStructureChanged method to indicate the number of rows or columns in the table have changed.

package auction;

import javax.swing.table.AbstractTableModel;
import javax.swing.event.TableModelEvent;
import java.text.NumberFormat; 
import java.util.*;
import java.awt.*;

public class ResultsModel extends AbstractTableModel{
  String[]  columnNames={};
  Vector rows = new Vector();

  public String getColumnName(int column) {
    if (columnNames[column] != null) {
      return columnNames[column];
    } else {
      return "";
    }
  }

  public boolean isCellEditable(int row, int column){
    return false;
  }

  public int getColumnCount() {
    return columnNames.length;
  }

  public int getRowCount() {
    return rows.size();
  }

  public Object getValueAt(int row, int column){
    Vector tmprow = (Vector)rows.elementAt(row);
    return tmprow.elementAt(column);          
  }

  public  void update(Enumeration enum) {
    try {
      columnNames = new String[5];     
      columnNames[0]=new String("Auction Id #");
      columnNames[1]=new String("Description");
      columnNames[2]=new String("High Bid");
      columnNames[3]=new String("# of bids");
      columnNames[4]=new String("End Date");
      while((enum !=null) && 
		(enum.hasMoreElements())) {
        while(enum.hasMoreElements()) {
          AuctionItem auctionItem=(
		AuctionItem)enum.nextElement();
          Vector items=new Vector(); 
          items.addElement(new Integer(
		auctionItem.getId()));       
          items.addElement(
		auctionItem.getSummary()); 
          int bidcount= auctionItem.getBidCount();                 
          if(bidcount >0) {
            items.addElement(
		NumberFormat.getCurrencyInstance().
		format(auctionItem.getHighBid()));
          } else {
            items.addElement("-");
          }
          items.addElement(new Integer(bidcount));                 
          items.addElement(auctionItem.getEndDate());
          rows.addElement(items);
        }
      }                       

      fireTableStructureChanged();
    } catch (Exception e) {
      System.out.println("Exception e"+e);
    }
  }
}
The table is created from the ResultsModel model. Then, the first table column is removed from that table and added to a new table. Because there are now two tables, the only way the selections can be kept in sync is to use a ListSelectionModel object to set the selection on the table row in the other tables that were not selected by calling the setRowSelectionInterval method.

The full example can be found in the AuctionClient.java source file:

  private void listAllItems() throws IOException{
    ResultsModel rm=new ResultsModel();
    if (!standaloneMode) {
      try {
         BidderHome bhome=(BidderHome) 
		ctx.lookup("bidder");
         Bidder bid=bhome.create();
         Enumeration enum=
		(Enumeration)bid.getItemList();
         if (enum != null) {
           rm.update(enum);
         }
       } catch (Exception e) {
         System.out.println(
		"AuctionServlet <list>:"+e);
       }
    } else {
      TestData td= new TestData();
      rm.update(td.results());
    }
    scrollTable=new JTable(rm);
    adjustColumnWidth(scrollTable.getColumn(
	"End Date"), 150);
    adjustColumnWidth(scrollTable.getColumn(
	"Description"), 120);
    scrollColumnModel = scrollTable.getColumnModel();
    fixedColumnModel = new DefaultTableColumnModel();
 
    TableColumn col = scrollColumnModel.getColumn(0);
    scrollColumnModel.removeColumn(col);
    fixedColumnModel.addColumn(col);
 
    fixedTable = new JTable(rm,fixedColumnModel);
    fixedTable.setRowHeight(scrollTable.getRowHeight());
    headers = new JViewport();
 
    ListSelectionModel fixedSelection = 
		fixedTable.getSelectionModel();
    fixedSelection.addListSelectionListener(
		new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        ListSelectionModel lsm = (
   	   ListSelectionModel)e.getSource();
        if (!lsm.isSelectionEmpty())  {
           setScrollableRow();
        }
      }
     });                                                   

     ListSelectionModel scrollSelection = 
		scrollTable.getSelectionModel();
     scrollSelection.addListSelectionListener(
		new ListSelectionListener() {
       public void valueChanged(ListSelectionEvent e) {
         ListSelectionModel lsm = 
		(ListSelectionModel)e.getSource();
         if (!lsm.isSelectionEmpty())  {
           setFixedRow();
         }
       }
     });

     CustomRenderer custom = new CustomRenderer();
     custom.setHorizontalAlignment(JLabel.CENTER);
     scrollColumnModel.getColumn(2).setCellRenderer(
		custom);             
     scrollColumnModel.getColumn(3).setCellRenderer(
		new CustomButtonRenderer());
   
     CustomButtonEditor customEdit=new 
			CustomButtonEditor(frame);
     scrollColumnModel.getColumn(3).setCellEditor(
		customEdit); 

     headers.add(scrollTable.getTableHeader());
 
     JPanel topPanel = new JPanel();
     topPanel.setLayout(new BoxLayout(topPanel, 
		BoxLayout.X_AXIS));
     adjustColumnWidth(
		fixedColumnModel.getColumn(0), 100);
 
     JTableHeader fixedHeader=
                    fixedTable.getTableHeader();
     fixedHeader.setAlignmentY(Component.TOP_ALIGNMENT);
     topPanel.add(fixedHeader);
     topPanel.add(Box.createRigidArea(
                        new Dimension(2, 0)));
     topPanel.setPreferredSize(new Dimension(400, 40));
 
     JPanel headerPanel = new JPanel();
     headerPanel.setAlignmentY(Component.TOP_ALIGNMENT);
     headerPanel.setLayout(new BorderLayout());
 
     JScrollPane scrollpane = new JScrollPane();
     scrollBar = scrollpane.getHorizontalScrollBar();
 
     headerPanel.add(headers, "North");
     headerPanel.add(scrollBar, "South");
     topPanel.add(headerPanel);
                                                                
     scrollTable.setPreferredScrollableViewportSize( 
		new Dimension(300,180));
     fixedTable.setPreferredScrollableViewportSize( 
		new Dimension(100,180));
     fixedTable.setPreferredSize( 
                new Dimension(100,180));
 
     innerPort = new JViewport();
     innerPort.setView(scrollTable);
     scrollpane.setViewport(innerPort);
 
     scrollBar.getModel().addChangeListener(
		new ChangeListener()  {
       public void stateChanged(ChangeEvent e) {
         Point q = headers.getViewPosition();
         Point p = innerPort.getViewPosition();
         int val = scrollBar.getModel().getValue();
         p.x = val;
         q.x = val;
         headers.setViewPosition(p);
         headers.repaint(headers.getViewRect());
         innerPort.setViewPosition(p);
         innerPort.repaint(innerPort.getViewRect());
       }
     });
 
     scrollTable.getTableHeader(
                   ).setUpdateTableInRealTime(
		   false);
 
     JPanel bottomPanel = new JPanel();
     bottomPanel.setLayout(new BoxLayout(
		bottomPanel, BoxLayout.X_AXIS));
     fixedTable.setAlignmentY(Component.TOP_ALIGNMENT);
     bottomPanel.add(fixedTable);
     bottomPanel.add(Box.createRigidArea(
                           new Dimension(2, 0)));
     innerPort.setAlignmentY(Component.TOP_ALIGNMENT);
     bottomPanel.add(innerPort);
     bottomPanel.add(Box.createRigidArea(
                           new Dimension(2, 0)));

     scrollPane= new JScrollPane(bottomPanel,
               JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,          
               JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
     JViewport outerPort = new JViewport();
     outerPort.add(bottomPanel);
     scrollPane.setColumnHeaderView(topPanel);
     scrollPane.setViewport(outerPort);
 
     scrollTable.setAutoResizeMode(
                   JTable.AUTO_RESIZE_OFF);
     frame.getContentPane().add(scrollPane);
 
     scrollTable.validate();
     frame.setSize(450,200);
  }
 
  void setFixedRow() {
    int index=scrollTable.getSelectedRow();
    fixedTable.setRowSelectionInterval(index, index);
  }
 
  void setScrollableRow() {
    int index=fixedTable.getSelectedRow();
    scrollTable.setRowSelectionInterval(index, index);
  }
 
  void adjustColumnWidth(TableColumn c, int size) {
    c.setPreferredWidth(size);
    c.setMaxWidth(size);
    c.setMinWidth(size);
  }                                          

JList Model

The JList component displays a vertical list of data elements and uses a ListModel to hold and manipulate the data. It also uses a ListSelectionModel object to enable selection and subsequent retrieval of elements in the list.

Default implementations of the AbstractListModel and AbstractListSelectionModel classes are provided in the Project Swing API in the form of the DefaultListModel and DefaultListSelectionModel classes. If you use these two default models and the default cell renderer, you get a list that displays model elements by calling the toString method on each object. The list uses the MULTIPLE_INTERVAL_SELECTION list selection model to select each element from the list.

Three selection modes are available to DefaultListSelectionModel: SINGLE_SELECTION, where only one item is selected at a time; SINGLE_INTERVAL_SELECTION in which a range of sequential items can be selected; and MULTIPLE_INTERVAL_SELECTION, which allows any or all elements to be selected. The selection mode can be changed by calling the setSelectionMode method in the JList class.

    public SimpleList() {
       JList list;
       DefaultListModel deflist;
       deflist= new DefaultListModel();
       deflist.addElement("element 1");
       deflist.addElement("element 2");
       list = new JList(deflist);
 
       JScrollPane scroll = new JScrollPane(list);
       getContentPane().add(scroll, BorderLayout.CENTER);
     }                                                     

JTree Model

The JTree class models and displays a vertical list of elements or nodes arranged in a tree-based hierarchy.

A JTree object has one root node and one or more child nodes, which can contain further child nodes. Each parent node can be expanded to show all its children similiar to directory trees familiar to Windows users.

Like the JList and JTable components, the JTree consists of more than one model. The selection model is similiar to the one detailed for the JList model. The selection modes have the following slightly different names: SINGLE_TREE_SELECTION, DISCONTIGUOUS_TREE_SELECTION, and CONTIGUOUS_TREE_SELECTION.

While DefaultTreeModel maintains the data in the tree and is responsible for adding and removing nodes, it is the DefaultTreeMutableTreeNode class that defines the methods used for node traversal. The DefaultTreeModel is often used to implement custom models because there is no AbstractTreeModel in the JTree package. However, if you use custom objects, you must implement TreeModel. This code example creates a JTree using the DefaultTreeModel.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;

public class SimpleTree extends JFrame {
  public SimpleTree() {
    String[] treelabels =   { 
			     "All Auctions", 
			     "Closed Auction", 
			     "Open Auctions"};
    Integer[] closedItems = { new Integer(500144), 
			      new Integer(500146), 
			      new Integer(500147)};

    Integer[] openItems = { new Integer(500148), 
			    new Integer(500149)};
                                             
    DefaultMutableTreeNode[] nodes = new 
        DefaultMutableTreeNode[treelabels.length];
    DefaultMutableTreeNode[] closednodes = new 
	DefaultMutableTreeNode[closedItems.length];
    DefaultMutableTreeNode[] opennodes = new 
	DefaultMutableTreeNode[openItems.length];

    for (int i=0; i < treelabels.length; i++) {
      nodes[i] = new 
        DefaultMutableTreeNode(treelabels[i]); 
    }
    nodes[0].add(nodes[1]);
    nodes[0].add(nodes[2]);

    for (int i=0; i < closedItems.length; i++) {
       closednodes[i] = new 
		DefaultMutableTreeNode(closedItems[i]); 
       nodes[1].add(closednodes[i]);
    }

    for (int i=0; i < openItems.length; i++) {
       opennodes[i] = new 
		DefaultMutableTreeNode(openItems[i]); 
       nodes[2].add(opennodes[i]);
    }
    DefaultTreeModel model=new 
                DefaultTreeModel(nodes[0]);

    JTree tree = new JTree(model);

    JScrollPane scroll = new JScrollPane(tree);
    getContentPane().add(scroll, BorderLayout.CENTER);
  }                                                               

  public static void main(String[] args) {
    SimpleTree frame = new SimpleTree();
    frame.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
        System.exit(0);
      }
    });
    frame.setVisible(true);
    frame.pack();
    frame.setSize(150,150);
  }                          
}
The toString method is used to retrieve the value for the Integer objects in the tree. And although the DefaultTreeModel is used to maintain the data in the tree and to add or remove nodes, the DefaultMutableTreeNode class defines the methods used to traverse through the nodes in the tree.

A primitive search of the nodes in a JTree is accomplished with the depthFirstEnumeration method, which is the same as the postorderEnumeration method and works its way from the end points of the tree first. Or you can call the preorderEnumeration method, the reverse of the postorderEnumeration method, which starts from the root and descends each tree in turn. Or you can call the breadthFirstEnumeration method, which starts from the root and visits all the child nodes in one level before visiting the child nodes at a lower depth.

The following code expands the parent node if it contains a child node that matches the search field entered. It uses a call to Enumeration e = nodes[0].depthFirstEnumeration(); to return a list of all the nodes in the tree. Once it has found a match, it builds the TreePath from the root node to the node that matched the search to pass to the makeVisible method in the JTree class that ensures the node is expanded in the tree.

import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;

public class SimpleSearchTree extends JFrame {
  JPanel findPanel;
  JTextField findField;
  JTree tree;
  JButton findButton;
  DefaultMutableTreeNode[] nodes;

  public SimpleSearchTree() {
    String[] treelabels =  { "All Auctions", 
			     "Closed Auction", 
			     "Open Auctions" };
    Integer[] closedItems = { new Integer(500144), 
			      new Integer(500146), 
			      new Integer(500147) };

    Integer[] openItems ={ new Integer(500148), 
			   new Integer(500149)};
                                             
    nodes = new 
      DefaultMutableTreeNode[treelabels.length];
    DefaultMutableTreeNode[] closednodes = new 
	    DefaultMutableTreeNode[closedItems.length];
    DefaultMutableTreeNode[] opennodes = new 
	    DefaultMutableTreeNode[openItems.length];
    for (int i=0; i < treelabels.length; i++) {
       nodes[i] = new 
         DefaultMutableTreeNode(treelabels[i]); 
    }
    nodes[0].add(nodes[1]);
    nodes[0].add(nodes[2]);

    for (int i=0; i < closedItems.length; i++) {
       closednodes[i] = new 
		DefaultMutableTreeNode(closedItems[i]); 
       nodes[1].add(closednodes[i]);
    }

    for (int i=0; i < openItems.length; i++) {
       opennodes[i] = new DefaultMutableTreeNode(
                            openItems[i]); 
       nodes[2].add(opennodes[i]);
    }

    DefaultTreeModel model=new 
                       DefaultTreeModel(nodes[0]);
    tree = new JTree(model);

    JScrollPane scroll = new JScrollPane(tree);
    getContentPane().add(scroll, BorderLayout.CENTER);
    findPanel= new JPanel();
    findField= new JTextField(10);
    findButton= new JButton("find");
    findButton.addActionListener (new ActionListener() {
      public void actionPerformed (ActionEvent e) {
        String field=findField.getText(); 
        if (field != null) {
          findNode(findField.getText());
        } else {
          return;
        } 
      }
    });
    findPanel.add(findField);
    findPanel.add(findButton);
    getContentPane().add(findPanel, BorderLayout.SOUTH);
  }                                                               
  public void findNode(String field) {
    Enumeration e = nodes[0].depthFirstEnumeration(); 
    Object currNode; 
    while (e.hasMoreElements()) {
      currNode = e.nextElement(); 
      if (currNode.toString().equals(field)) { 
        TreePath path=new TreePath(((
	     DefaultMutableTreeNode)currNode).getPath());
        tree.makeVisible(path);
        tree.setSelectionRow(tree.getRowForPath(path));  
        return;
      } 
    } 
  }

  public static void main(String[] args) {
    SimpleSearchTree frame = new SimpleSearchTree();
    frame.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
        System.exit(0);
      }
    });
    frame.setVisible(true);
    frame.pack();
    frame.setSize(300,150);
  }                          
}
JTree, JTable and JList are probably the most common models you will want to customize. But you can use models such as SingleSelectionModel for general data manipulation. The SingleSelectionModel class lets you specify how data is selected in a component.

Custom Cell Rendering

As you learned above, many components have a default cell renderer to paint each element in a table, tree or list. The default cell renderer is usually a JLabel and displays a String representation of the data element.

A simple custom cell renderer can extend the DefaultXXXCellRenderer class to provide additional customization in the getXXXCellRenderer. The DefaultTableCellRenderer and DefaultTreeCellRenderer Components both use a JLabel to render the cell. This means any customization that can be applied to a JLabel can also be used in the JTable or JTree cell.

For example, the following renderer sets the background color of the component if the auction item has received a high number of bids:

class CustomRenderer extends DefaultTableCellRenderer {
  public Component getTableCellRendererComponent(
                     JTable table,Object value, 
                     boolean isSelected, 
                     boolean hasFocus, 
                     int row, int column) {

       Component comp = 
                   super.getTableCellRendererComponent(
		     table,value,isSelected,hasFocus,
		     row,column);

       JLabel label = (JLabel)comp;

       if(((Integer)value).intValue() >= 30) {
           label.setIcon(new ImageIcon("Hot.gif"));
       } else {
           label.setIcon(new ImageIcon("Normal.gif"));
       }

       return label;                   
   }
}                            
The renderer is set on a column like this:
  CustomRenderer custom = new CustomRenderer();
  custom.setHorizontalAlignment(JLabel.CENTER);
  scrollColumnModel.getColumn(2).setCellRenderer(
                                          custom);
If the component being displayed inside the JTable column requires more functionality than is available using a JLabel, you can create your own TableCellRenderer. This next code example uses a JButton as the renderer cell.
class CustomButtonRenderer extends JButton 
		implements TableCellRenderer {
   public CustomButtonRenderer() {
     setOpaque(true);
   }
 
   public Component getTableCellRendererComponent(
                      JTable table, Object value, 
                      boolean isSelected, 
                      boolean hasFocus, int row, 
                      int column) {

      if (isSelected) {
         ((JButton)value).setForeground(
		table.getSelectionForeground());
         ((JButton)value).setBackground(
		table.getSelectionBackground());
      } else {
         ((JButton)value).setForeground(table.getForeground());
         ((JButton)value).setBackground(table.getBackground());
      }
      return (JButton)value;
    }
}                                 
Like the default JLabel cell renderer, this class relies on an underlying component (in this case JButton) to do the painting. Selection of the cell toggles the button colors. As before, the cell renderer is secured to the appropriate column of the auction table with the setCellRenderer method:
   scrollColumnModel.getColumn(3).setCellRenderer(
	new CustomButtonRenderer()); 
Alternately, all JButton components can be configured to use the CustomButtonRenderer in the table with a call to setDefaultRenderer as follows:
  table.setDefaultRenderer(
	JButton.class, new CustomButtonRenderer());

Custom Cell Editing

In the same way that you can configure how a cell is painted in a JTable or JTree component, you can also configure how an editable cell responds to edits. One difference between using cell editors and cell renderers is there is a DefaultCellEditor for all components, but no DefaultTableCellEditor for table cells.

While separate renderers exist for JTree and JTable, a single DefaultCellEditor class implements both the TableCellEditor and TreeCellEditor interfaces. However, the DefaultCellEditor class has constructors for only the JComboBox, JCheckBox, and JTextField components. The JButton class does not map to any of these constructors so a dummy JCheckBox is created to satisfy the requirements of the DefaultCellEditor class.

This next example uses a custom button editor that displays the number of days left in the auction when the button is double clicked. The double click to trigger the action is specified by setting the value clickCountToStart to two. An exact copy of the getTableCellEditorComponent method paints the button in edit mode. A JDialog component that displays the number of days left appears when the getCellEditorValue method is called. The value for the number of days left is calculated by moving the current calendar date towards the end date. The Calendar class does not have a method that expresses a difference in two dates in anything other than the milliseconds between those two dates.

class CustomButtonEditor extends DefaultCellEditor {
  final JButton mybutton;
  JFrame frame;
 
  CustomButtonEditor(JFrame frame) {
    super(new JCheckBox());
    mybutton = new JButton();
    this.editorComponent = mybutton;
    this.clickCountToStart = 2;
    this.frame=frame;
    mybutton.setOpaque(true);
    mybutton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        fireEditingStopped();
      }
    });
  }
 
  protected void fireEditingStopped() {
    super.fireEditingStopped();
  }
 
  public Object getCellEditorValue() {
    JDialog jd= new JDialog(frame, "Time left");
    Calendar today=Calendar.getInstance();
    Calendar end=Calendar.getInstance();
    SimpleDateFormat in=new SimpleDateFormat("yyyy-MM-dd");
    try {
      end.setTime(in.parse(mybutton.getText()));
    } catch (Exception e){
      System.out.println("Error in date"+mybutton.getText()+e);
    }
    int days = 0;                                             
    while(today.before(end)) {
    today.roll(Calendar.DATE,true);
    days++;
    }
    jd.setSize(200,100);
    if (today.after(end)) {
       jd.getContentPane().add(new JLabel("Auction completed"));
    } else {
      jd.getContentPane().add(new JLabel("Days left="+days));
    }                    
    jd.setVisible(true);
     return new String(mybutton.getText());
  }
 
  public Component getTableCellEditorComponent(JTable table, 
		Object value, boolean isSelected, 
		int row, int column) {
 
    ((JButton) editorComponent).setText(((
		JButton)value).getText());
    if (isSelected) {
      ((JButton) editorComponent).setForeground(
		table.getSelectionForeground());
      ((JButton) editorComponent).setBackground(
		table.getSelectionBackground());
    } else {
      ((JButton) editorComponent).setForeground(
		table.getForeground());
      ((JButton) editorComponent).setBackground(
		table.getBackground());
    }
    return editorComponent;
    }
 }                                             

Specialized Event Handling

Project Swing uses the event handling classes available in the AWT API since JDK 1.1. However, some new APIs are available in the SwingUtilities class that are used to add some control over the event queue. The two new event handling methods are invokeLater and invokeAndWait. The invokeAndWait method waits for the event to be processed in the event queue.

These methods are often used to request focus on a component after another event has occurred that might affect the component focus. You can return the focus by calling the invokeLater method and passing a Thread:

  JButton button =new JButton();
  SwingUtilities.invokeLater(new Runnable() { 
    public void run() { 
      button.requestFocus();
    }
  }); 

Project Swing Directions

While the basic architecture of Project Swing has stayed true to its original design, many optimizations and improvements have been made to components like JTable and in areas such as scrolling. Add to this the Java HotSpotTM Performance Engine, which greatly reduces the cost of object creation, and Project Swing can boast its best performance to date.

However, as seen in the Analyze a Program section in the Performance chapter, a simple 700x300 table requires nearly half a megabyte of memory when double buffered. The creation of ten tables would probably require swapping memory to disk, severly affecting performance on low-end machines.

[TOP]


[ This page was updated: 3-Nov-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.