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

Swing, Custom List Selection Model for JList Objects

Java Programming, Lecture Notes # 212, Revised 08/22/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.

Upgraded for JDK 1.2 compatibility on 3/22/99.
 

Introduction

The material in this lesson assumes that you have studied my earlier lesson on Understanding Component MVC Models as well as my lesson on Swing, Custom Rendering of JList Cells.

The sample program in this lesson is a modification of the sample program in the rendering 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 in the earlier lesson.  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

The lessons mentioned above discuss in detail the first and third capabilities involving how widgets look and their data representation.

The primary topic of this lesson is the second capability in the list:  controlling how widgets respond to input.  I will show you how you can control the rules for selection of elements in a JList component by defining and installing a custom list selection model.

You will also 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 if you plan to compile and run the sample program.
 

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:

Swing widgets, such as JList, are subclasses of JComponent. At any given point in time, a JComponent object is associated with a single data model and a single delegate.  We have been discussing these associations in previous lessons.  Some components, such as JList, are also associated with models of other types, such as list selection models, which manage the rules of selection within the list.  This lesson shows you how to define and install a custom list selection model to modify the selection rules.

A list selection model for a JList must be an object of a class that implements the ListSelectionModel interface.  Starting from scratch and defining a complete implementation of that interface would be a challenge because it contains several fields and a large number of methods including the methods to add and remove ListSelectionListener objects.

Most of the methods declared in the interface are shown in the following table to illustrate my point.
 

addListSelectionListener(ListSelectionListener x) - Add a listener to the list that's notified each time a change to the selection occurs.
addSelectionInterval(int index0, int index1) - Change the selection to be the set union of the current selection and the indices between index0 and index1 inclusive. 
clearSelection() - Change the selection to the empty set. 
getAnchorSelectionIndex() - Return the first index argument from the most recent call to setSelectionInterval() or addSelectionInterval().
getLeadSelectionIndex() - Return the second index argument from the most recent call to setSelectionInterval() or addSelectionInterval().
getMaxSelectionIndex() - Returns the last selected index or -1 if the selection is empty.
getMinSelectionIndex() - Returns the first selected index or -1 if the selection is empty.
setValueIsAdjusting(boolean valueIsAdjusting) - This property is true if upcoming changes to the value of the model should be considered a single event. 
getValueIsAdjusting() - Returns a boolean indicating whether the value is currently being adjusted.
insertIndexInterval(int index, int length, boolean before) - Insert length indices beginning before/after index. 
isSelectedIndex(int index) - Returns true if the specified index is selected.
isSelectionEmpty() - Returns true if no indices are selected.
removeIndexInterval(int index0, int index1) - Remove the indices in the interval index0,index1 (inclusive) from the selection model. 
removeListSelectionListener(ListSelectionListener x) - Remove a listener from the list that's notified each time a change to the selection occurs.
removeSelectionInterval(int index0, int index1) - Change the selection to be the set difference of the current selection and the indices between index0 and index1 inclusive. 
setAnchorSelectionIndex(int index) - Set the anchor selection index.
setLeadSelectionIndex(int index) - Set the lead selection index.
setSelectionInterval(int index0, int index1) - Change the selection to be between index0 and index1 inclusive. 
setSelectionMode(int selectionMode) - The following selectionMode values are allowed: 
SINGLE_SELECTION Only one list index can be selected at a time. In this mode the setSelectionInterval and addSelectionInterval methods are equivalent, and only the second index argument (the "lead index") is used.  SINGLE_INTERVAL_SELECTION One contiguous index interval can be selected at a time. In this mode setSelectionInterval and addSelectionInterval are equivalent.  MULTIPLE_INTERVAL_SELECTION In this mode, there's no restriction on what can be selected. 
getSelectionMode() - Returns an  integer defining the current selection mode.

You have two options for defining a class that will serve as a list selection model for a Jlist.  One option is to implement the interface described above.

Swing provides a default list selection model class that can be used with JList.  The second option is to extend the default model class taking advantage some of the existing methods, and overriding others.

Unlike the data model discussed in a previous lesson, Swing does not provide an abstract implementation of the ListSelectionModel interface that can be extended to define your own model.  However, you can extend the default model class named DefaultListSelectionModel to define your new model, which is a very practical approach.  With this approach, you only override those methods for which you want to modify the selection rules, and you leave the remaining methods alone.

There are basically two steps involved in defining and using a custom list selection model.  First you must define a class that implements the ListSelectionModel interface (or extend the DefaultListSelectionModel class overriding methods as appropriate).

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

public void setSelectionModel(ListSelectionModel selectionModel)

Set the selectionModel for the list to a non-null ListSelectionModel implementation.

The selection model handles the task of making single selections, selections of contiguous ranges, and non-contiguous selections. 

Once you have done these two things, the selection of elements in your JList object will follow the new rules defined in your model.
 

Sample Program

The name of this program is SwingList04.java. It is a modification of the program named SwingList03.

The purpose of the program is to illustrate the definition and use of a custom list selection model for a Swing JList component.  It also illustrates some other features as well:

The program populates two JList objects with identical data.  The two lists are identical except for their appearance and the manner in which elements can be selected in the list.

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.  This list incorporates normal rules for selecting an element in the list using the mouse or the arrow keys.

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.

Further, and this is the primary purpose of this lesson, the list with the icons uses a custom list selection model that provides a selection rule that is different from the normal selection rules.

The selection rule for this list causes the item actually selected to be the one immediately above the one that you click with the mouse or select with an arrow key unless it is the top element in the list.

This is accomplished by providing an overridden setSelectionInterval() method in the custom selection model that decrements the index limits of the selection unless they already have a value of zero.

Note that this effectively disables the down-arrow key for the purpose of selecting an element in the list.  When you press the down arrow to select the next element, the selection pops back up to the original element.  When you use the up-arrow key to select elements in the list, the actual selections occur on every other element.

The purpose of this custom list selection model is not to create confusion in the selection process.  Rather, the purpose is to illustrate that the JList component uses a separate model to execute the selection rules, and you can modify those rules by defining and installing a custom version of the selection model if that suits your needs.

Both lists use the DefaultListModel as a data model 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. Also, both will throw an exception if you attempt to remove an element with an invalid index value in the associated text field.

The data to populate the two lists is automatically obtained from the file and directory names in 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 (keeping in mind the modified selection rule discussed above).  The index number of the element actually selected 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 Pluggable Look and Feel (PLAF) control panel is also displayed on the screen.  You can change the look and feel by clicking a button on the control panel.

This program was tested using JDK 1.1.6 and Swing 1.0.3 under Win95. A modified version was tested using JDK 1.2. Modification required changing import directives to match Swing requirements under JDK 1.2.
 

Interesting Code Fragments

This program, named SwingList04, is a modification of the program named SwingList03 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 instantiation of the two JList objects using the DefaultListModel as their data model.

More importantly, this fragment invokes the setSelectionModel() method on the first list to cause the custom list selection model to be used on that list.  Note that the list selection model for the second list remains the default model for comparison.
 

      listA = new JList(new DefaultListModel());
      listA.setCellRenderer(new CustomCellRenderer());
      //Put a custom selection model on this list.
      listA.setSelectionModel(new CustomSelectionModel());
      
      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 list selection model object.  Note that this class extends the DefaultListSelectionModel class which in turn implements the ListSelectionModel interface.

The new class overrides the setSelectionInterval() method in the superclass to provide the modified selection rules discussed earlier.  Once an object of this class is defined as the list selection model for a list (as in the previous fragment) this overridden method is invoked in place of the original method in the superclass.  As you can see, if the index values are not zero, they are decremented, and then the same method in the superclass is invoked with the modified index values.

This results in the behavior that the element actually selected is immediately above the one clicked with the mouse (index value is decremented) whenever the user attempts to select an element in the list.
 

  class CustomSelectionModel 
                         extends DefaultListSelectionModel{
  
    public void setSelectionInterval(
                                    int index0,int index1){
      if(index0 != 0) index0--;
      if(index1 != 0) index1--;
      super.setSelectionInterval(index0,index1);      
    }//end setSelectionInterval()
  }//end class CustomSelectionModel

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

This section contains a complete listing of the program.

/*File SwingList04.java

Rev 03/24/99

Upgraded for JDK1.2 compatibility on 3/24/99.

This is a modification of the program named SwingList03.
This is a modification of the program named SwingList03.

The purpose of the program is to illustrate the definition
and use of a custom list selection model for a Swing
JList component.

It also illustrates custom cell rendering for JList 
components.

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 
and the manner in which elements can be selected in the
list.

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. This list
incorporates normal rules for selecting an element in the
list using the mouse or the arrow keys.

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.

Further, the list with the icons uses a custom list 
selection model that provides a different set of selection
rules.  

The selection rule for this list causes the item actually 
selected to be the one immediately above the one that you 
click with the mouse or select with an arrow key unless it
is the top element in the list.  This is accomplished by
providing an overridden method in the custom selection 
model that subtracts a value of one from the index limits 
of the selection unless they already have a value of zero.

Note that this effectively disables the down-arrow key for 
the purpose of selecting an element in the list.  When you 
use the up-arrow key to select elements in the list, the 
actual selections occur on every other element.

The purpose is not to create confusion in the selection
process.  Rather, the purpose is to illustrate
that the JList component uses a separate model to execute
the selection rules, and you can modify those rules by
defining and installing a custom version of the selection 
model.

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. Also, both will throw an exception if you
attempt to remove an element with an invalid index value
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.

(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.)
  
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. 

Tested using JDK 1.1.6 and Swing 1.0.3 under 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 SwingList04 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.
  SwingList04 thisObj;
  
  //-----------------------------------------------------//
  
  public static void main(String args[]) {
    //Instantiate the top-level JFrame object.
    new SwingList04();
  }//end main
  //-----------------------------------------------------//

  public SwingList04 () {//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());
      //Put a custom selection model on this list.
      listA.setSelectionModel(new CustomSelectionModel());
      
      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  
  //=====================================================//

  //Inner class to implement a custom selection model. 
  // This custom selection model modifies the selection
  // rules defined by the DefaultListSelectionModel by
  // extending that class and overriding one of the 
  // methods of that class.  Unless the index values 
  // defining a selection interval are equal to zero, they
  // are reduced by a value of 1 and then passed to the
  // same method in the superclass.  If the index value is
  // zero, it is not modified.
  class CustomSelectionModel 
                         extends DefaultListSelectionModel{
  
    public void setSelectionInterval(
                                    int index0,int index1){
      if(index0 != 0) index0--;
      if(index1 != 0) index1--;
      super.setSelectionInterval(index0,index1);      
    }//end setSelectionInterval()
  }//end class CustomSelectionModel
  
  //=====================================================//
  
}//end class SwingList04

-end-