Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Swing, Custom Rendering of JList Cells

Java Programming, Lecture Notes # 211, Revised 03/24/99.


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on October 3, 1998, using the JDK 1.1.6 download package along with Swing 1.0.3. The purpose of this lesson is to illustrate the use of MVC models with Swing components.

The sample program was upgraded to JDK 1.2 on 3/24/99 by modifying the import directives to make them compatible with the version of Swing contained in JDK 1.2.
 

Introduction

The material in this lesson assumes that you have studied my earlier lesson on Understanding Component MVC Models.  The sample program in this lesson is a modification of the sample program in that lesson.  In this lesson, I will discuss only the new material in the program.  To understand the entire program, you may need to refer to the discussion of the original program.  It will also be helpful if you have studied my earlier lesson on using the JList class to create simple lists.

Swing gives you the ability to control how widgets look, how they  respond to input, and in some cases, how their data is represented.  The lesson mentioned above discussed the third capability involving data representation in detail.

The primary topic of this lesson is the first capability in the list:  controlling how widgets look.  I will show you how you can control the appearance of the elements in a JList object from two different aspects.  The first aspect has to do with the use of Pluggable Look and Feel (PLAF).  I have discussed this topic in several previous lessons that you may need to go back and review.  You will need to go back to the lesson entitled Swing, Hidden Buttons with Icons, Icon Images, Borders, Tool Tips, Nested Buttons, and Other Fun Stuff and extract the code from that lesson for the class named PlafPanel02.java.  You will need that code in this lesson.

PLAF really isn't new to this lesson.  I included it in this lesson simply for completeness.  The second aspect, and the new information in this lesson is how to create and use a custom cell rendering class to cause the cells in the JList object to be rendered according to your specifications.
 

Discussion

I will use an object of the JList class as the vehicle for this discussion, although the general concepts discussed here apply to several of the more complex Swing components including:

If you don't provide a custom cell rendering class, the cells in a JList object are rendered as black text on a white or colored background.  Depending on the look and feel implementation being used, selecting an element in the list causes its color or the color of its background to change in an obvious way.  The sample program in this lesson creates two different JList objects for comparison.  One uses default rendering and the other uses a custom cell renderer.

There are basically two steps involved in creating and using a custom cell renderer.  First you must define a class that implements the ListCellRenderer interface.  This interface declares one method as described below:
 

public java.awt.Component getListCellRendererComponent(JList list,
                                              java.lang.Object value,
                                              int index,
                                              boolean isSelected,
                                              boolean cellHasFocus)

Parameters:
        list - The JList being rendered.
        value - The value returned by list.getModel().getElementAt(index).
        index - The index of the cell being rendered.
        isSelected - True if the specified cell was selected.
        cellHasFocus - True if the specified cell has the focus.
Returns:
        A component whose paint() method will render the specified value.

This method returns a Component object designed to display the specified value in the appropriate manner. That component's paint method is then called to "render" the cell.  As is usual, you can use any of the incoming parameters to accomplish your goal.  The sample program in this lesson uses the two parameters, value and isSelected, to display the cell's text and an icon, which toggles between two different images depending on whether or not the cell is selected.

The second step is to invoke the following method on the JList object passing an object of your new cell renderer class as a parameter.
 

public void setCellRenderer(ListCellRenderer cellRenderer)

Sets the delegate that's used to paint each cell in the list. If prototypeCellValue was set then the fixedCellWidth and fixedCellHeight properties are set as well. Only one PropertyChangeEvent is generated however for the "cellRenderer" property. 

The default value of this property is provided by the ListUI delegate, i.e. by the look and feel implementation. 

This is a JavaBeans bound property.

Parameters:
cellRenderer - the ListCellRenderer that paints list cells

Once you have done these two things, the cells in your list will be rendered using your new cell renderer object.
 

Sample Program

The name of this program is SwingList03.java.  The purpose is to illustrate custom cell rendering for a Swing JList component.

It also illustrates the use of the ListSelectionListener interface to instantiate listener objects that monitor for the selection of an element in the list, retrieve the index value of the element, and display the value.

It also illustrates the ability to remove an element from a JList object.

This program populates two JList objects with identical data for comparison purposes. The data used to populate the two lists is obtained from the names of the files and directories in the root directory of the C-drive.

The two lists are identical except for their appearance.  One of the lists renders each element simply as text.  For this list, the background color for an element changes to a different color when the element is selected.

The other list renders each list element as an icon and some text.  When an element is selected, the icon is a small red ball.  When an element is not selected, the icon is a small blue ball.  You will need to furnish your own images for the icons, so your images will probably be different.

Both lists use the DefaultListModel that has the ability to remove an element from the list. Both will throw an exception if you attempt to remove an element from an empty list.  They will also throw an exception if you attempt to remove an element with an invalid index in the associated text field.

The data to populate the two lists is automatically obtained from the root directory on the C-drive.  When the program starts, the two populated lists, two buttons, and two text fields appear on the screen.  The buttons and text fields should appear to the right of the list with which they are associated.

To remove an element from a list, enter the index in the associated text field and click the associated button.

To determine the index value of an element, simply select it with the mouse or an arrow key.  The index number will be placed in the text field.  Clicking the associated button at that point will cause the selected element to be removed from the list.  You can also manually enter an index value into the text field causing an element other than the selected element to be removed.

A PLAF control panel is also displayed on the screen.  You can change the look and feel by clicking a button on the control panel.

The program was tested using JDK 1.1.6 and Swing 1.0.3 under Win95. A revised version also tested using JDK 1.2.
 

Interesting Code Fragments

This is a modification of the program named SwingList02 from a previous lesson.  Most of the code is identical to, or very similar to the code in that program.  In this lesson, I will discuss only the code that is new or significantly different from the earlier program.  You can view all of the code in the listing of the program that follows later.

The first fragment shows the beginning of the constructor and the statement to instantiate a PLAF control panel and link it to this application.  As mentioned earlier, you will need to extract the code for PlafPanel02 from an earlier lesson.
 

  public SwingList03 () {//constructor
    new PlafPanel02(this);

The next fragment shows the instantiation of the two JList objects using the DefaultListModel as their data model.

More importantly, this fragment invokes the setCellRenderer() method on the first list to cause the custom cell renderer to be used whenever it is necessary to redraw a cell in that list.
 

      listA = new JList(new DefaultListModel());
      listA.setCellRenderer(new CustomCellRenderer());

      listB = new JList(new DefaultListModel());

Now I am going to skip all the way down to the definition of the class used to instantiate the custom cell renderer object.  Note that this class implements the ListCellRenderer interface that declares only one method named getListCellRendererComponent.

Once an object of this class is defined as the cell renderer for a list (as in the previous fragment) this method is invoked whenever the system needs to render a cell in that list.  The method returns a Component object whose paint() method is used to render the cell.

With this definition of the method, the cell is rendered using a JLabel, because this class extends JLabel and returns this.  The code in the method causes the text in the JLabel to be the toString() representation of the value received as a parameter, and causes the icon to be one of two choices depending on the state of the incoming parameter named isSelected.  Once you know the purpose of the method, the actual code in this method should be pretty familiar to you so I won't discuss it further.
 

  class CustomCellRenderer extends JLabel 
                               implements ListCellRenderer{
    ImageIcon selected = new ImageIcon("red-ball.gif");
    ImageIcon unSelected = new ImageIcon("blue-ball.gif");

    public Component getListCellRendererComponent(
      JList list,          // the list being redrawn
      Object value,        // value to display
      int index,           // cell index
      boolean isSelected,  // is the cell selected
      boolean cellHasFocus)// the list and the cell have 
                           //  the focus
    {//begin getListCellRendererComponent() body
       String theLabel = value.toString();
       setText(theLabel);
       if(isSelected){//set the red ball
         setIcon(selected);
       }else{//set the blue ball for not selected
         setIcon(unSelected);
       }//end else
       return this;//return component used to render
    }//end getListCellRendererComponent()
  }//end class CustomCellRenderer  

As mentioned earlier, you will need to provide your own images.  Just substitute the names of your image files in place of those in the fragment to use your images in place of mine.

The code in the program that was not highlighted in the fragments above can be viewed in the complete listing of the program that follows in the next section.
 

Program Listing

A complete program listing is provided in this section.

/*File SwingList03.java

Rev 03/24/99

Upgraded to JDK 1.2 on 3/24/99
This is a modification of the program named SwingList02.

The purpose of this program is to illustrate custom cell 
rendering for a Swing JList component.

It also illustrates the use of the ListSelectionListener
interface to instantiate listener objects that monitor
for the selection of an element in the list, retrieve the
index value of the element, and display the value.

This program should be discussed only after the student
understands how to create and use JList objects as
described in SwingList01.java.

This program populates two JList objects with identical
data. The data used to populate the two lists is obtained
from the names of the files and directories in the root
directory of the C-drive.

Both of the JList objects are based on the 
DefaultListModel.

The two lists are identical except for their appearance.  
One of the lists renders each element simply as text.  For
this list, the background for an element changes to a
different color when the element is selected.

The other list renders each list element as an icon and
some text.  When an element is selected, the icon is a
small red ball.  When an element is not selected, the icon
is a small blue ball.  You will need to furnish your own
images for the icons, so your images will probably be
different.

Both lists use the DefaultListModel which has the ability
to remove an element from the list. (Both will throw an 
exception if you attempt to remove an element from an 
empty list.)

The data to populate the two lists is automatically
obtained from the root directory on the C-drive.  When the
program starts, the two populated lists, two buttons, and 
two text fields appear on the screen.  The buttons and 
text fields should appear to the right of the list with 
which they are associated.

To remove an element from a list, enter the index in the
associated text field and click the associated button.

To determine the index value of an element, simply select
it with the mouse or an arrow key.  The index number will
be placed in the text field.  Clicking the associated 
button at that point will cause the selected element to be
removed from the list.  You can also manually enter an 
index value into the text field causing an element other
than the selected element to be removed.

(The program runs under JDK 1.1.6 whether compiled using
the JDK or the Microsoft jvc.  However, it won't run
under my current version of Microsoft jview even when 
compiled using jvc.  It throws an exception in the area
of populating the Vector object.)

Tested using JDK 1.1.6 and Swing 1.0.3 under Win95.
Also tested using JDK 1.2 and Win95.
**********************************************************/
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
//import com.sun.java.swing.*; //JDK1.1/Swing1.0.3 version
//import com.sun.java.swing.tree.*;
//import com.sun.java.swing.event.*;
import javax.swing.*; //JDK1.2 version
import javax.swing.tree.*;
import javax.swing.event.*; //=======================================================// public class SwingList03 extends JFrame{   JList listA;   JList listB;   JScrollPane scrollPaneA;   JScrollPane scrollPaneB;   MyListSelectionListener listSelectionListenerA;   MyListSelectionListener listSelectionListenerB;   JPanel panelA = new JPanel();   JPanel panelB = new JPanel();   JButton buttonA;   JButton buttonB;   JTextField fieldA;   JTextField fieldB;      Dimension prefSize;      //The following is a reference to the top-level JFrame   // which contains everything else.   SwingList03 thisObj;      //-----------------------------------------------------//      public static void main(String args[]) {     //Instantiate the top-level JFrame object.     new SwingList03();   }//end main   //-----------------------------------------------------//   public SwingList03 () {//constructor        //Instantiate and link to a Programmable Look & Feel     // control panel.     new PlafPanel02(this);          getContentPane().setLayout(new FlowLayout());     //Create a Vector object containing data for     // populating two JList objects.     Vector theFileNames = new Vector();     String dir = "c:/";     String[] fileList = new File(dir).list();//dir listing     //Loop and process each file in the directory     for(int fileCnt = 0;fileCnt<fileList.length;fileCnt++){       if(new File(              dir + "/" + fileList[fileCnt]).isDirectory()){         theFileNames.addElement("dir: " +                                          fileList[fileCnt]);       }else{         theFileNames.addElement("file: " +                                          fileList[fileCnt]);       }//end else     }//end for loop     if(theFileNames != null){       //Create and populate two JList objects             listA = new JList(new DefaultListModel());       //Put a custom cell renderer on this list.       listA.setCellRenderer(new CustomCellRenderer());       listB = new JList(new DefaultListModel());              //Give them names to be used in event handler       listA.setName("MyNameIsListA");       listB.setName("MyNameIsListB");       //Populate the list       Enumeration theEnum = theFileNames.elements();       while(theEnum.hasMoreElements()){         Object theObject = theEnum.nextElement();         ((DefaultListModel)listA.getModel()).                                      addElement(theObject);         ((DefaultListModel)listB.getModel()).                                      addElement(theObject);       }//end while loop         //Display listA, its button, and its textField       scrollPaneA = new JScrollPane(listA);       panelA.add(scrollPaneA);       listSelectionListenerA =                               new MyListSelectionListener();       listA.addListSelectionListener(                                    listSelectionListenerA);       listA.setVisibleRowCount(6);       //Allow selection of one element index at a time.       listA.setSelectionMode(                       ListSelectionModel.SINGLE_SELECTION);       //Get size of list and adjust JPanel accordingly       prefSize =                  listA.getPreferredScrollableViewportSize();       panelA.setBounds(0,0,prefSize.width,prefSize.height);       getContentPane().add(panelA);              buttonA = new JButton("A");       getContentPane().add(buttonA);       fieldA = new JTextField("Field A");       getContentPane().add(fieldA);         //Listener to remove elements from listA       buttonA.addActionListener(new ActionListener() {         public void actionPerformed(ActionEvent e) {           int theElement =                          Integer.parseInt(fieldA.getText());           ((DefaultListModel)listA.getModel()).                            removeElementAt(theElement);}});              //Display listB       scrollPaneB = new JScrollPane(listB);       panelB.add(scrollPaneB);       listSelectionListenerB =                               new MyListSelectionListener();       listB.addListSelectionListener(                                    listSelectionListenerB);       listB.setVisibleRowCount(6);       //Allow selection of one element index at a time.       listB.setSelectionMode(ListSelectionModel.                                          SINGLE_SELECTION);       //Get size of list and adjust JPanel accordingly       prefSize =                  listB.getPreferredScrollableViewportSize();       panelB.setBounds(0,0,prefSize.width,prefSize.height);       getContentPane().add(panelB);              buttonB = new JButton("B");       getContentPane().add(buttonB);       fieldB = new JTextField("Field B");       getContentPane().add(fieldB);       //Listener to remove elements from listA       buttonB.addActionListener(new ActionListener() {         public void actionPerformed(ActionEvent e) {           int theElement =                          Integer.parseInt(fieldB.getText());           ((DefaultListModel)listB.getModel()).                            removeElementAt(theElement);}});            }//end if(theFileNames != null)     //Save a reference to the top-level JFrame object     // in an instance variable for later use.     thisObj = this;     setTitle("Copyright 1998, R.G.Baldwin");       setSize(400,350);     setVisible(true);     //An anonymous inner class to terminate the program     // when the     // user clicks the close button on the frame.     this.addWindowListener(new WindowAdapter() {       public void windowClosing(WindowEvent e) {         System.exit(0);}     });        }//end constructor      //=====================================================//      //Inner class to monitor for selection events on the   // JList object and store the index of the selected   // element in the JTextField associated with the JList.   class MyListSelectionListener                            implements ListSelectionListener{     public void valueChanged(ListSelectionEvent e){       int selectedIndex =                   ((JList)e.getSource()).getSelectedIndex();       //Don't allow the list to exist without a selected       // element giving a selected index of -1.  This will        // throw an exception if the user attempts to remove       // an element from an empty list, but a negative       // index will also throw an exception if the user       // attempts to remove an element with a negative       // index.       if(((Component)e.getSource()).getName().                           compareTo("MyNameIsListA") == 0){         if(selectedIndex < 0) listA.setSelectedIndex(0);         else fieldA.setText("" + selectedIndex);       }else{         if(selectedIndex < 0) listB.setSelectedIndex(0);         else fieldB.setText("" + selectedIndex);       }//end if     }//end valueChanged()   }//end class MyListSelectionListener   //=====================================================//      //Inner class to implement a custom cell renderer   class CustomCellRenderer extends JLabel                                 implements ListCellRenderer{     ImageIcon selected = new ImageIcon("red-ball.gif");     ImageIcon unSelected = new ImageIcon("blue-ball.gif");     //This method is declared by the ListCellRenderer     // interface.  Once it is defined as the cell renderer     // for a list, it is called each time it is necessary     // to redraw the cell.  With this definition, the     // cell is rendered using a JLabel object containing     // an icon and some text.  The text is the original     // text.  The icon toggles between a red ball when     // the element is selected and a blue ball when the     // element is not selected.     public Component getListCellRendererComponent(       JList list,          // the list being redrawn       Object value,        // value to display       int index,           // cell index       boolean isSelected,  // is the cell selected       boolean cellHasFocus)// the list and the cell have                             //  the focus     {//begin getListCellRendererComponent() body        String theLabel = value.toString();        setText(theLabel);        if(isSelected){//set the red ball          setIcon(selected);        }else{//set the blue ball for not selected          setIcon(unSelected);        }//end else        return this;//return component used to render     }//end getListCellRendererComponent()   }//end class CustomCellRenderer     //=====================================================//    }//end class SwingList03

-end-