Java Swing: Mastering JList
Java Swing: Mastering JList
List Boxes
In this chapter:
JList
Custom renderer
10.1
class javax.swing.JList
This class represents a basic GUI component allowing the selection of one or more
items from a list of choices. JList has two models: ListModel which handles data in the
list, and ListSelectionModel which handles item selection (three different selection
modes are supported which we will discuss below). JList also supports custom
rendering, as we learned in the last chapter, through the implementation of the
ListCellRenderer interface. We can use existing default implementation of
ListCellRenderer (DefaultListCellRenderer) or create our own according to our
particular needs (which we will see later in ths chapter). Note that unless we use a
custom renderer, the default renderer will display each element as a String defined by
that objects toString() method (the only exceptions to this are Icon implementations
which will be renderered as they would be in any JLabel). Also note that a
ListCellRenderer returns a Component, but that component is not interactive and is
only used for display purposes (i.e. it acts as a rubber stamp API). For instance, if a
JCheckBox is used as a renderer we will not be able to check and uncheck it. Unlike
JComboBox, however, JList does not support editing of any sort.
A number of constructors are available to create a JList component. We can use the
default constructor or pass list data to a constructor as a one-dimensional array, a
Vector, or as an implementation of the ListModel interface. The last variant provides
maximum control over a list's properties and appearance. We can also assign data to a
JList using the setModel() method, or one of the overloaded setListData() methods.
JList does not provide direct access to its elements, and we must access its ListModel
to gain access to this data. JList does, however, provide direct access to its selection
data by implementing all ListSelectionModel methods, and delegating their traffic to
the actual ListSelectionModel instance. To avoid repetition we will discuss selection
funcionality below in our overview of ListSelectionModel.
JList maintains selection foreground and background colors (assigned by its UI
delegate when installed), and the default cell renderer, DefaultListCellRenderer, will
use these colors to render selected cells. These colors can be assigned with
setSelectedForeground() and setSelectedBackground(). Nonselected cells will be
1
rendered with the component foreground and background colors assigned to JList with
setForeground() and setBackground().
JList implements the Scrollable interface (see chapter 7) to provide vertical unit
incremental scrolling corresponding the list cell height, and vertical block incremental
scrolling corresponding to the number of visible cells. Horizontal unit increment scrolling
corresponds to the size of the lists font (1 if the font is null), and horizontal block unit
increment scrolling corresponds to the current width of the list. Thus JList does not
directly support scrolling and is intended to be placed in a JScrollPane.
The visibleRowCount property specifies how many cells should be visible when a JList
is placed in a scroll pane. This defaults to 8 and can be set with the
setVisibleRowCount() method. Another interesting method provided by JList is
ensureIndexIsVisible(), which forces the list to scroll itself so that the element
corresponding to the given index becomes visible. Note that JList also supports
autoscrolling (i.e. it will scroll element by element every 100ms if the mouse is dragged
below or above its bounds and if an item has been selected).
By default the width of each cell is the width of the widest item, and the hieght of each
cell corresponds to the hieght of the tallest item. We can overpower this behavior and
specify our own fixed cell width and height of each list cell using the
setFixedCellWidth() and setFixedCellHeight() methods.
Another way to control the width and height of each cell is through use of the
setPrototypeCellValue() method. This method takes an Object parameter and uses it
to automatically determine the fixedCellWidth and fixedCellHeight. A typical use of
this method would be to give it a String. This forces the list to use a fixed cell width
and hieght equal to the width and hieght of that string when rendered in the Font
currently assigned to the JList.
JList also provides a method called locationToIndex() which will return the index of a
cell at the given Point (in list coordinates). 1 will be returned if none is found.
Unfortunately JList does not provide support for double-clicking, but this method
comes in very handy in implementing our own. The following psuedocode shows how
we can use a MouseAdapter, MouseEvent, and the locationToIndex() method to
determine the JList cell a double-click occurs on:
myJist.addMouseListener(newMouseAdapter(){
publicvoidmouseClicked(MouseEvente){
if(e.getClickCount()==2){
intcellIndex=myJList.locationToIndex(e.getPoint());
//Wenowhavetheindexofthedoubleclickedcell..
}
}
});
10.1.1
any changes that occur to this model. Note that this interface leaves the job of
specifying how we store and structure the data, as well as how we add, remove, or
change an item, completely up to its implementations.
10.1.2 AbstractListModel
abstract class javax.swing.AbstractListModel
This class represents a partial implementation of the ListModel interface. It defines the
default
event
handling
functionality,
and
implements
the
add/remove
ListDataListener methods, as well as methods to fire ListDataEvents (see below)
when additions, removals, and changes occur. The remainder of ListModel, methods
getElementAt() and getSize(), must be implemented in any concrete sub-class.
10.1.3
DefaultListModel
class javax.swing.DefaultListModel
This class represents the concrete default implementation of the ListModel interface. It
extends AbstractListModel and uses a java.util.Vector to store its data. Almost all
of the methods of this class correspond directly to Vector methods and we will not
discuss them here. Familiarity with Vectors implies familiarity with how
DefaultListModel works (see API docs).
10.1.4
below) are provided as well. Each of these methods is self-explanitory in the API docs
and we will not describe them in detail here.
3
JList defines all the methods declared in this interface and simply delegates all traffic
to its ListSelectionModel instance. This allows access to selection data without the
10.1.5
DefaultListSelectionModel
class javax.swing.DefaultListSelectionModel
This class represents the concrete default implementation of the ListSelectionModel
interface. It defines methods to fire ListSelectionEvents (see below) when a selection
range changes.
10.1.6
10.1.7
10.1.8 ListDataEvent
class javax.swing.event.ListDataEvent
This class represents the event delivered when changes occur in a list's ListModel. It
includes the source of the event as well as the index of the lowest and highest indexed
elements affected by the change. It also includes the type of event that occurred. Three
ListDataEvent types are defined as static ints: CONTENTS_CHANGED, INTERVAL_ADDED,
and INTERVAL_REMOVED. We can use the getType() method to discover the type of any
ListDataEvent.
10.1.9
10.1.10
ListSelectionEvent
class javax.swing.event.ListSelectionEvent
This class represents an event delivered by ListSelectionModel when changes occur in
its selection. It is almost identical to ListDataEvent, except that the indices specified
signify where there has been a change in the selection model, rather than the data
model.
4
10.2
This example displays a list of the united states using an array of Strings in the
following format:
2-character abbreviation<tab character>full name<tab character>capital
<<file figure10-1.gif>>
The Code: StatesList.java
see \Chapter10\1
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importjavax.swing.*;
importjavax.swing.border.*;
importjavax.swing.event.*;
publicclassStatesListextendsJFrame
{
protectedJListm_statesList;
publicStatesList(){
super("SwingList[Base]");
setSize(500,240);
String[]states={
"AK\tAlaska\tJuneau",
"AL\tAlabama\tMontgomery",
"AR\tArkansas\tLittleRock",
"AZ\tArizona\tPhoenix",
"CA\tCalifornia\tSacramento",
"CO\tColorado\tDenver",
"CT\tConnecticut\tHartford",
"DE\tDelaware\tDover",
"FL\tFlorida\tTallahassee",
"GA\tGeorgia\tAtlanta",
"HI\tHawaii\tHonolulu",
"IA\tIowa\tDesMoines",
"ID\tIdaho\tBoise",
"IL\tIllinois\tSpringfield",
"IN\tIndiana\tIndianapolis",
"KS\tKansas\tTopeka",
"KY\tKentucky\tFrankfort",
"LA\tLouisiana\tBatonRouge",
"MA\tMassachusetts\tBoston",
"MD\tMaryland\tAnnapolis",
"ME\tMaine\tAugusta",
6
"MI\tMichigan\tLansing",
"MN\tMinnesota\tSt.Paul",
"MO\tMissouri\tJeffersonCity",
"MS\tMississippi\tJackson",
"MT\tMontana\tHelena",
"NC\tNorthCarolina\tRaleigh",
"ND\tNorthDakota\tBismarck",
"NE\tNebraska\tLincoln",
"NH\tNewHampshire\tConcord",
"NJ\tNewJersey\tTrenton",
"NM\tNewMexico\tSantaFe",
"NV\tNevada\tCarsonCity",
"NY\tNewYork\tAlbany",
"OH\tOhio\tColumbus",
"OK\tOklahoma\tOklahomaCity",
"OR\tOregon\tSalem",
"PA\tPennsylvania\tHarrisburg",
"RI\tRhodeIsland\tProvidence",
"SC\tSouthCarolina\tColumbia",
"SD\tSouthDakota\tPierre",
"TN\tTennessee\tNashville",
"TX\tTexas\tAustin",
"UT\tUtah\tSaltLakeCity",
"VA\tVirginia\tRichmond",
"VT\tVermont\tMontpelier",
"WA\tWashington\tOlympia",
"WV\tWestVirginia\tCharleston",
"WI\tWisconsin\tMadison",
"WY\tWyoming\tCheyenne"
};
m_statesList=newJList(states);
JScrollPaneps=newJScrollPane();
ps.getViewport().add(m_statesList);
getContentPane().add(ps,BorderLayout.CENTER);
WindowListenerwndCloser=newWindowAdapter(){
publicvoidwindowClosing(WindowEvente){
System.exit(0);
}
};
addWindowListener(wndCloser);
setVisible(true);
}
publicstaticvoidmain(Stringargv[]){
newStatesList();
}
}
Class StatesList
Class StatesList extends JFrame to implement the frame container for this example.
One instance variable, JList m_statesList, is used to store an array of state Strings
(as described above). This list is created by passing the states String array to the
JList constructor. It is then added to a JScrollPane instance to provide scrolling
capabilities.
7
10.3
Custom rendering
In this section well add the ability to allign Strings containing tab separators into a
table-like arrangement. We want each tab character to shift all text to its right, to a
specified location instead of being rendered as the square symbol we saw above. These
locations should be determined uniformly for all elements of the list to form columns
that line up correctly.
Note that this example works well with proportional fonts as well as with fixed width
fonts (i.e. it doesnt matter what font we use because alignment is not designed to be
font-dependent). This makes JList a powerful but simple component, which can be
used in place of JTable in simple cases such as the example presented here (where the
involvement of JTable would create unnecessary overhead).
To accomplish the desired rendering we construct a custom renderer,
TabListCellRenderer, which exposes accessor methods to specify and retreive tab
positions based on the index of a tab character in a String being rendered:
getDefaultTab()/setDefaultTab(int) : manages the default tab size (defaults to 50).
In case a position is not specified for a given tab index, we use a default size to
determine how far to offset a portion of text.
getTabs()/setTabs(int[]): manages an array of positions based on the index of a
tab character in a String being rendered. These positions used in rendering each
<<file figure10-2.gif>>
The Code: StatesList.java
see \Chapter10\2
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importjavax.swing.*;
importjavax.swing.border.*;
importjavax.swing.event.*;
publicclassStatesListextendsJFrame
{
protectedJListm_statesList;
publicStatesList(){
//Unchangedcodefromsection10.2
m_statesList=newJList(states);
TabListCellRendererrenderer=newTabListCellRenderer();
renderer.setTabs(newint[]{50,200,300});
m_statesList.setCellRenderer(renderer);
//Unchangedcodefromsection10.2
}
}
classTabListCellRendererextendsJLabel
implementsListCellRenderer
{
protectedstaticBorderm_noFocusBorder;
protectedFontMetricsm_fm=null;
protectedInsetsm_insets=newInsets(0,0,0,0);
protectedintm_defaultTab=50;
protectedint[]m_tabs=null;
publicTabListCellRenderer(){
super();
m_noFocusBorder=newEmptyBorder(1,1,1,1);
setOpaque(true);
setBorder(m_noFocusBorder);
}
publicComponentgetListCellRendererComponent(JListlist,
Objectvalue,intindex,booleanisSelected,booleancellHasFocus)
{
setText(value.toString());
setBackground(isSelected?list.getSelectionBackground():
list.getBackground());
setForeground(isSelected?list.getSelectionForeground():
list.getForeground());
setFont(list.getFont());
setBorder((cellHasFocus)?UIManager.getBorder(
"List.focusCellHighlightBorder"):m_noFocusBorder);
9
returnthis;
}
publicvoidsetDefaultTab(intdefaultTab){
m_defaultTab=defaultTab;
}
publicintgetDefaultTab(){returnm_defaultTab;}
publicvoidsetTabs(int[]tabs){m_tabs=tabs;}
publicint[]getTabs(){returnm_tabs;}
publicintgetTab(intindex){
if(m_tabs==null)
returnm_defaultTab*index;
intlen=m_tabs.length;
if(index>=0&&index<len)
returnm_tabs[index];
returnm_tabs[len1]+m_defaultTab*(indexlen+1);
}
publicvoidpaint(Graphicsg){
m_fm=g.getFontMetrics();
g.setColor(getBackground());
g.fillRect(0,0,getWidth(),getHeight());
getBorder().paintBorder(this,g,0,0,getWidth(),getHeight());
g.setColor(getForeground());
g.setFont(getFont());
m_insets=getInsets();
intx=m_insets.left;
inty=m_insets.top+m_fm.getAscent();
StringTokenizerst=newStringTokenizer(getText(),"\t");
while(st.hasMoreTokens()){
StringsNext=st.nextToken();
g.drawString(sNext,x,y);
x+=m_fm.StringWidth(sNext);
if(!st.hasMoreTokens())
break;
intindex=0;
while(x>=getTab(index))
index++;
x=getTab(index);
}
}
}
Class StatesList
Minor changes have been made to this class (compared to StatesList from the
previous section). We create an instance of our custom TabListCellRenderer, pass it an
array of positions and set it as the renderer for our JList component.
10
Class TabListCellRenderer
Class TabListCellRenderer extends JLabel and implements the ListCellRenderer
interface to be used as our custom renderer.
Class variable:
Borderm_noFocusBorder: border to be used when a list item has no focus.
Instance variables:
FontMetricsm_fm: used in calculating text positioning when drawing.
Insetsm_insets: insets of the cell being rendered.
intm_defaultTab: default tab size.
int[]m_tabs: an array of positions based on tab index in a String being rendered.
The constructor creates assigns text, sets its opaque property to true (to render the
component's area with the specified background), and sets the border to
m_noFocusBorder.
The getListCellRendererComponent() method is required when implementing
ListCellRenderer, and is called each time a cell is about to be rendered. It takes five
parameters:
JListlist: reference to the list instance.
Objectvalue: data object to be painted by the renderer.
intindex: index of the item in the list.
booleanisSelected: true if the cell is currently selected.
booleancellHasFocus:true if the cell currently has the focus.
Our implementation of this method assigns new text, sets the background and
foreground (depending on whether or not the cell is selected), sets the font to that
taken from the parent list component, and sets the border according to whether or not
the cell has input focus.
Four additional methods provide set/get support for the m_defaultTab and m_tabs
variables, and do not require detailed explanation beyond the code listing. Now let's
take a close look at the getTab() method which calculates and returns the position for a
given tab index. If no tab array, m_tabs, is set, this method returns the m_defaultTab
distance (defaults to 50) multiplied by the given tab index. If the m_tabs array is not
null and the tab index is less than it's length, the proper value from that array is
returned. Otherwise, if the tab index is greater than the array's length, we have no
choice but to use the default tab size again.
Since the JLabel component does not render tab characters properly, we do not benefit
a lot from its inheritance and implement the paint() method to draw tabbed Strings
ourselves.
Note: Because this is a very simple component that we do not plan to enhance with custom
UI functionality, overriding paint() is acceptable.
11
First, our paint() method requests a reference to the FontMetrics instance for the
given Graphics. Then we fill the component's rectangle with the background color
(which is set in the getListCellRendererComponent() method depending on whether or
not the cell is selected, see above), and paint the component's border.
Note: Alternatively we could use the drawTabbedText() method from the
javax.swing.text.Utilities class to draw tabbed text. However, this requires us to
implement the TabExpander interface. In our case it's easier to draw text directly without
using that utility. As an interesting exercise you can modify the code from this example to
use drawTabbedText() method. We will discuss working with tabs more in chapter 19.
In the next step we prepare to draw the tabbed String. We set the foreground color,
font, and determine the initial x and y positions for drawing the text, taking into account
the component's insets.
Reminder: To draw text in Java you need to use a baseline y-coordinate. This is why the
getAscent() value is added to the y position. The getAscent() method returns the
distance from the font's baseline to the top of most alphanumeric characters. See chapter 2
for more information on drawing text and Java 2 FontMetrics caveats.
We then use a StringTokenizer to parse the String and extract the portions separated
by tabs. Each portion is drawn with the drawString() method, and the x-coordinate is
adjusted to the length of the text. We cycle through this process, positioning each
portion of text by calling the getTab() method, until no more tabs are found.
Running the Code
Figure 10.2 shows StatesList displaying an array of tab-separated Strings. Note that
the tab symbols are not drawn directly, but form consistently aligned columns inside
the list.
UI Guideline : Improved Balance
With the tab character now being displayed correctly, the list box now has much better
balance. The available area for Capital City is still very large and as designer you may wish
to consider reduing this, thus reducing the excessive white space to the right hand side.
Such a decision would normally be made after the List Box is seen in situation and
necessary alignment and overall panel balance is taken into consideration.
10.4
In this section we will continue to enhance our JList states example by adding the
ability to select an element whose text starts with a character corresponding to a key
press. We will also show how to extend this functionality to search for an element
whose text starts with a sequence of typed key characters.
To do this, we must use a KeyListener to listen for keyboard input, and accumulate this
input in a String. Each time a key is pressed, the listener must search through the list
and select the first element whose text matches the String that we have accumulated.
If the time interval between two key presses exceeds a certain pre-defined value, the
accumulated String must be cleared before appending a new character to avoid
overflow.
12
Figure 10.3 JList allowing accumulated keyboard input to search for a matching item.
<<file figure10-3.gif>>
The Code: StatesList.java
see \Chapter10\3
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importjavax.swing.*;
importjavax.swing.border.*;
importjavax.swing.event.*;
publicclassStatesListextendsJFrame
{
protectedJListm_statesList;
publicStatesList(){
//Unchangedcodefromsection10.3
m_statesList=newJList(states);
TabListCellRendererrenderer=newTabListCellRenderer();
renderer.setTabs(newint[]{50,200,300});
m_statesList.setCellRenderer(renderer);
m_statesList.addKeyListener(newListSearcher(m_statesList));
//Unchangedcodefromsection10.3
}
}
classListSearcherextendsKeyAdapter
{
protectedJListm_list;
protectedListModelm_model;
protectedStringm_key="";
protectedlongm_time=0;
publicstaticintCHAR_DELTA=1000;
publicListSearcher(JListlist){
m_list=list;
m_model=m_list.getModel();
13
}
publicvoidkeyTyped(KeyEvente){
charch=e.getKeyChar();
if(!Character.isLetterOrDigit(ch))
return;
if(m_time+CHAR_DELTA<System.currentTimeMillis())
m_key="";
m_time=System.currentTimeMillis();
m_key+=Character.toLowerCase(ch);
for(intk=0;k<m_model.getSize();k++){
Stringstr=((String)m_model.getElementAt(k)).toLowerCase();
if(str.startsWith(m_key)){
m_list.setSelectedIndex(k);
m_list.ensureIndexIsVisible(k);
break;
}
}
}
}
Class StatesList
An instance of ListSearcher is added to the m_statesList component as a
KeyListener. This is the only difference made to this class with respect to the previous
example.
Class ListSearcher
Class ListSearcher extends the KeyAdapter class and defines on class variable:
intCHAR_DELTA: static variable to hold the maximum time interval in ms between two
subsequent key presses before clearing the search key character String.
Instance variables:
JList m_list: list component to search and change selection based on keyboard
input.
ListModelm_model: list model of m_list.
Stringm_key: key character String used to search for a match.
longm_time: time in ms of the last key press.
The ListSearcher constructor simply takes a reference to the parent JList component
and stores it in instance variable m_list, and its model in m_model.
The keyTyped() method is called each time a new character is typed (i.e. a key is
pressed and released). Our implementation first obtains a typed character and returns if
that character is not letter or digit. keyTyped() then checks the time interval between
now and the time when the previous key type event occurred. If this interval exceeds
CHAR_DELTA, the m_key String is cleared. Finally, this method walks through the list and
performs a case-insensitive comparison of the list Strings and searching String
(m_key). If an elements text starts with m_key, this element is selected and it is forced
to appear within our current JList view using the ensureIndexIsVisible() method.
14
10.5
Lists can certainly be used for more than just Strings. We can easily imagine a list of
Swing components. A list of check boxes is actually common in software packages when
prompting for selection of optional constituents during installation. In Swing such a list
can be constructed by implementing a custom renderer that uses the JCheckBox
component. The catch is that mouse and keyboard events must be handled manually to
check/uncheck these boxes.
The following example shows how to create a list of check boxes representing imaginary
optional program constituents. Associated with each component is an instance of our
custom InstallData class with the following fields:
Field
m_name
m_size
m_selected
Type
String
int
boolean
Description
Component's name
Component's size in KB
true if component is selected
<<file figure10-4.gif>>
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importjavax.swing.*;
importjavax.swing.border.*;
importjavax.swing.event.*;
publicclassCheckBoxListextendsJFrame
{
protectedJListm_list;
protectedJLabelm_total;
publicCheckBoxList(){
super("SwingList[Checkboxes]");
setSize(280,250);
getContentPane().setLayout(newFlowLayout());
InstallData[]options={
newInstallData("Programexecutable",118),
newInstallData("Helpfiles",52),
newInstallData("Toolsandconverters",83),
newInstallData("Sourcecode",133)
};
m_list=newJList(options);
CheckListCellRendererrenderer=newCheckListCellRenderer();
m_list.setCellRenderer(renderer);
m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
CheckListenerlst=newCheckListener(this);
m_list.addMouseListener(lst);
m_list.addKeyListener(lst);
JScrollPaneps=newJScrollPane();
ps.getViewport().add(m_list);
m_total=newJLabel("Spacerequired:0K");
JPanelp=newJPanel();
p.setLayout(newBorderLayout());
p.add(ps,BorderLayout.CENTER);
p.add(m_total,BorderLayout.SOUTH);
p.setBorder(newTitledBorder(newEtchedBorder(),
"Pleaseselectoptions:"));
getContentPane().add(p);
WindowListenerwndCloser=newWindowAdapter(){
publicvoidwindowClosing(WindowEvente){
System.exit(0);
}
};
addWindowListener(wndCloser);
setVisible(true);
recalcTotal();
}
publicvoidrecalcTotal(){
16
ListModelmodel=m_list.getModel();
inttotal=0;
for(intk=0;k<model.getSize();k++){
InstallDatadata=(InstallData)model.getElementAt(k);
if(data.isSelected())
total+=data.getSize();
}
m_total.setText("Spacerequired:"+total+"K");
}
publicstaticvoidmain(Stringargv[]){
newCheckBoxList();
}
}
classCheckListCellRendererextendsJCheckBox
implementsListCellRenderer
{
protectedstaticBorderm_noFocusBorder=
newEmptyBorder(1,1,1,1);
publicCheckListCellRenderer(){
super();
setOpaque(true);
setBorder(m_noFocusBorder);
}
publicComponentgetListCellRendererComponent(JListlist,
Objectvalue,intindex,booleanisSelected,booleancellHasFocus)
{
setText(value.toString());
setBackground(isSelected?list.getSelectionBackground():
list.getBackground());
setForeground(isSelected?list.getSelectionForeground():
list.getForeground());
InstallDatadata=(InstallData)value;
setSelected(data.isSelected());
setFont(list.getFont());
setBorder((cellHasFocus)?
UIManager.getBorder("List.focusCellHighlightBorder")
:m_noFocusBorder);
returnthis;
}
}
classCheckListenerimplementsMouseListener,KeyListener
{
protectedCheckBoxListm_parent;
protectedJListm_list;
publicCheckListener(CheckBoxListparent){
m_parent=parent;
m_list=parent.m_list;
}
publicvoidmouseClicked(MouseEvente){
if(e.getX()<20)
17
doCheck();
}
publicvoidmousePressed(MouseEvente){}
publicvoidmouseReleased(MouseEvente){}
publicvoidmouseEntered(MouseEvente){}
publicvoidmouseExited(MouseEvente){}
publicvoidkeyPressed(KeyEvente){
if(e.getKeyChar()=='')
doCheck();
}
publicvoidkeyTyped(KeyEvente){}
publicvoidkeyReleased(KeyEvente){}
protectedvoiddoCheck(){
intindex=m_list.getSelectedIndex();
if(index<0)
return;
InstallDatadata=(InstallData)m_list.getModel().
getElementAt(index);
data.invertSelected();
m_list.repaint();
m_parent.recalcTotal();
}
}
classInstallData
{
protectedStringm_name;
protectedintm_size;
protectedbooleanm_selected;
publicInstallData(Stringname,intsize){
m_name=name;
m_size=size;
m_selected=false;
}
publicStringgetName(){returnm_name;}
publicintgetSize(){returnm_size;}
publicvoidsetSelected(booleanselected){
m_selected=selected;
}
publicvoidinvertSelected(){m_selected=!m_selected;}
publicbooleanisSelected(){returnm_selected;}
publicStringtoString(){returnm_name+"("+m_size+"K)";}
}
Class CheckBoxList
CheckBoxList extends JFrame to provide the basic frame for this example. Instance
variables:
18
selected constituents.
An array of four InstallData objects is passed to the constructor of our JList
component (note that we use the DefaultListModel, which is sufficient for our
purposes here). SINGLE_SELECTION is used as our lists selection mode. An instance of
our custom CheckListCellRenderer is created and set as the cell renderer for our list.
An instance of our custom CheckListener is then registered as both a mouse and key
listener to handle item checking / unchecking for each check box (see below).
The list component is added to a JScrollPane to provide scrolling capabilities. Then
JLabelm_total is created to display the total amount of space required for installation
based on the currently selected check boxes.
In previous examples the JList component occupied all of our frame's available space.
In this example, however, we are required to consider a different layout. JPanelp is
now used to hold both the list and label ( m_total). To ensure that the label will always
be placed below the list we use a BorderLayout. We also use a TitledBorder for this
panels border to provide visual grouping.
Method recalcTotal() steps through the sequence of InstallData instances contained
in the list, and calculates the sum of sizes of the selected items. The result is then
displayed in the m_total label.
Class CheckListCellRenderer
This class implements the ListCellRenderer interface and is similar to our
TabListCellRenderer class from section 10.3. An important difference is that
CheckListCellRenderer extends JCheckBox (not JLabel) and uses that component to
render each item in our list. Method getListCellRendererComponent() sets the check
box text, determines whether or not the current list item is selected, and sets the check
boxs selection state accordingly (using its inherited JCheckBox.setSelected()
method).
Note: Alternatively we could use JLabels with custom icons to imitate checked and
unchecked boxes. However, the use of JCheckBox is preferred for graphical consistency
with other parts of a GUI.
Class CheckListener
This class implements both MouseListener and KeyListener to process all user input
which can change the state of check boxes in the list. Its constructor takes a
CheckBoxList
instance as parameter in order to gain access to the
CheckBoxList.recalcTotal() method.
Weve assumed in this example, that an item's checked state should be changed if:
1. The user clicks the mouse close enough to the item's check box (say, up to 20
pixels from the left edge).
2. The user transfers focus to the item (with the mouse or keyboard) and then presses
the space bar.
19
Class InstallData
Class InstallData handles a data unit for this example (it functions as a custom
model). InstallData encapsulates three variables described at the beginning of this
section: m_name, m_size, and m_selected. Its only constructor takes three parameters to
fill these variables. Besides the obvious set/get methods, the invertSelected() method
is defined to negate the value of m_selected. Method toString() determines the
String representation of this object to be used by the list renderer.
Running the Code
Figure 10.4 shows our list composed of check boxes in action. Select any item and click
over the check box, or press space bar to change its checked state. Note that the total
kilobytes required for these imaginary implementations is dynamically displayed in the
label at the bottom.
UI Guideline : When to use Check Boxes in a List Check boxes tend to be used inside
bordered panes to show groupings of mutually related binary attributes. Such a technique
is good for a fixed number of attributes, however, it becomes problematic when the number
of items can vary.
The technique shown here is a good way to solve the problem when the collection of
attributes or data is of an undetermined size. Use a CheckBox list for binary (True/False)
selection of items from a collection of a size which cannot be determined at design time.
For example, imagine the team selection for a football team. The coach has a pool of
players and needs to indicate who has been picked for the Saturday game. With such a
problem, you could show the whole pool of players (sorted alphabetically or by number) in
the list and allow the coach to check off each selected player.
20