0% found this document useful (0 votes)
111 views29 pages

Chapter 9. Combo Boxes: 9.1 Jcombobox

This chapter discusses combo boxes in Java Swing. It covers the JComboBox class, combo box models like ComboBoxModel and DefaultComboBoxModel, renderers using ListCellRenderer, and editors using ComboBoxEditor. Custom models, renderers, and editors can be used to modify the combo box's appearance and behavior.

Uploaded by

leenarajput
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
111 views29 pages

Chapter 9. Combo Boxes: 9.1 Jcombobox

This chapter discusses combo boxes in Java Swing. It covers the JComboBox class, combo box models like ComboBoxModel and DefaultComboBoxModel, renderers using ListCellRenderer, and editors using ComboBoxEditor. Custom models, renderers, and editors can be used to modify the combo box's appearance and behavior.

Uploaded by

leenarajput
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 29

Chapter 9.

Combo Boxes
In this chapter:

JComboBox

Basic JComboBox example

Custom model and renderer

Combo box with memory

Custom editing

9.1 JCombobox
class javax.swing.JComboBox
This class represents a basic GUI component which consists of two parts:
A popup menu (an implementation of javax.swing.plaf.basic.ComboPopup ). By
default this is a JPopupMenu sub-class (javax.swing.plaf.basic.BasicComboPopup )
containing a JList in a JScrollPane.
A button acting as a container for an editor or renderer component, and an arrow
button used to display the popup menu.
The JList uses a ListSelectionModel (see chapter 10) allowing SINGLE_SELECTION
only. Apart from this, JComboBox directly uses only one model, a ComboBoxModel, which
manages data in its JList.
A number of constructors are available to build a JComboBox. The default constructor
can be used to create a combo box with an empty list, or we can pass data to a
constructor as a one-dimensional array, a Vector, or as an implementation of the
ComboBoxModelinterface (see below). The last variant allows maximum control over the
properties and appearance of a JComboBox, as we will see.
As other complex Swing components, JComboBox allows a customizable renderer for
displaying each item in its drop-down list (by default a JLabel sub-class implementation
of ListCellRenderer), and a customizable editor to be used as the combo boxs data
entry component (by default an instance of ComboBoxEditor which uses a JTextField).
We can use the existing default implementations of ListCellRenderer and
ComboBoxEditor, or we can 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 renderer returns a Component, but that
component is not interactive and is only used for display purposes (i.e. it acts as a
rubber stampAPI). For instance, if a JCheckBox is used as a renderer we will not be able
to check and uncheck it. Editors, however, are fully interactive.
1

Similar to JList (next chapter), this class uses ListDataEvents to deliver information
about changes in the state of its drop-down lists model. ItemEvents and ActionEvents
are fired when the current selection changes (from any source--programmatic or direct
user input). Correspondingly, we can attach ItemListeners and ActionListeners to
receive these events.
The drop-down list of a JComboBox is a popup menu containing a JList (this is actually
defined in the UI delegate, not the component itself) and can be programmatically
displayed using the showPopup() and hidePopup() methods. As any other Swing popup
menu (which we will discuss in chapter 12), it can be displayed as either heavyweight
or lightweight. JComboBox provides the setLightWeightPopupEnabled() method allowing
us to choose between these modes.
JComboBox also defines an inner interface called KeySelectionManager that declares one
method, selectionForKey(char aKey, ComboBoxModel aModel) , which should be

overriden to return the index of the list element to select when the list is visible (popup
is showing) and the given keyboard character is pressed.
The JComboBox UI delegate represents JComboBox graphically by a container with a
button which encapsulates an arrow button and either a renderer displaying the
currently selected item, or an editor allowing changes to be made to the currently
selected item. The arrow button is displayed on the right of the renderer/editor and will
show the popup menu containing the drop-down list when clicked.
Note: Because of the JComboBox UI delegate construction, setting the border of a JComboBox
does not have the expected effect. Try this and you will see that the container containing
the main JComboBox button gets the assigned border, when in fact we want that button to
recieve the border. There is no easy way to set the border of this button without
customizing the UI delegate, and we hope to see this limitation disappear in a future
version.

When a JComboBox is editable (which it is not by default) the editor component will allow
modification of the currenly selected item. The default editor will appear as a
JTextField accepting input. This text field has an ActionListener attached that will
accept an edit and change the selected item accoringly when/if the Enter key is
pressed. If the focus changes while editing, all editing will be cancelled and a change
will not be made to the selected item.
JComboBox can be made editable with its setEditable() method, and we can specify a
custom ComboBoxEditor with JComboBoxs setEditor() method.. Setting the editable
property to true causes the UI delegate to replace the renderer component in the
button to the specified editor component. Similarly, setting this property to false

causes the editor in the button to be replaced by a renderer.


The cell renderer used for a JComboBox can be assigned/retrieved with the
setRenderer()/getRenderer() methods. Calls to these methods actually get passed to
the JList contained in the combo boxs popup menu.

9.1.1 The ComboBoxModel interface


abstract interface javax.swing.ComboBoxModel
This interface extends the ListModel interface which handles the combo box drop-down
list's data. This model separately handles its selected item with two methods,
setSelectedItem() and getSelectedItem().

9.1.2 The MutableComboBoxModel interface


abstract interface javax.swing.MutableComboBoxModel
This interface extends ComboBoxModel and adds four methods to modify the model's
contents
dynamically:
addElement(),
insertElementAt(),
removeElement(),
removeElementAt().

9.1.3 DefaultComboBoxModel
class javax.swing.DefaultComboBoxModel
This class represents the default model used by JComboBox, and implements
MutableComboBoxModel. To programmatically select an item we can call its
setSelectedItem() method. Calling this method, as well as any of the
MutableComboBoxModel methods mentioned above, will cause a ListDataEvent to be
fired. To capture these events we can attatch ListDataListeners with
DefaultComboBoxModels addListDataListener() method. We can also remove these
listeners with its removeListDataListener() method.

9.1.4 The ListCellRenderer interface


abstract interface javax.swing.ListCellRenderer
This is a simple interface used to define the component to be used as a renderer for the
JComboBox
drop-down
list.
It
declares
one
method,
getListCellRendererComponent(JList list, Object value, int Index, boolean
isSelected,booleancellHasFocus) , which is called to return the component used to
represent a given combo box element visually. The component returned by this method
is not at all interactive and is used for display purposes only (referred to as a rubber
stamp in the API docs).
When in noneditable mode, 1 will be passed to this method to return the component
used to represent the selected item in the main JComboBox button. Normally this
component is the same as the component used to display that same element in the
drop-down list.

9.1.5. DefaultListCellRenderer
class javax.swing.DefaultListCellRenderer
This is the concrete implementation of the ListCellRenderer interface used by JList
by default (and this by JComboBoxs JList). This class extends JLabel and its
getListCellRenderer() method returns a this reference, renders the given value by
setting its text to the String returned by the values toString() method (unless the
value is an instance of Icon, in which case it will be rendered as it would be in any
JLabel), and uses JList foreground and background colors depending on whether or
3

not the given item is selected.


Note: Unfortunately there is no easy way to access JComboBoxs drop-down JList, which
prevents us from assigning new foreground and background colors. Ideally JComboBox
would provide this communication with its JList, and we hope to see this functionality in a
future version.

A single static EmptyBorder instance is used for all cells that do not have the current
focus. This border has top, bottom, left, and right spacing of 1, and unfortunately
cannot be re-assigned.

9.1.6 The ComboBoxEditor interface


abstract interface javax.swing.ComboBoxEditor
This interface describes the JComboBox editor. The default editor is provided by the only
implementing class, javax.swing.plaf.basic.BasicComboBoxEditor . But we are
certainly not limited to this component. It is the purpose of this interface to allow us to
implement our own custom editor. The getEditorComponent() method should be
overridden to return the editor component to use. BasicComboBoxEditors
getEditorComponent() method returns a JTextField that will be used for the currently
selected combo box item. Unlike cell renderers, components returned by the
getEditorComponent() method are fully interactive and do not act like rubber stamps.
The setItem() method is intended to tell the editor which element to edit (this is called
when an item is selected from the drop-down list). The getItem() method is used to
return the object being edited (a String using the default editor).
The selectAll() method is intended to select all items to be edited, and the default
editor implements this by selecting all text in the text field (though this method is not
used in the default implementation, we might consider calling it from our own the
setItem() method to show all text selected when editing starts).
ComboBoxEditor also decalres functionality for attaching and removing ActionListeners

which are notified when an edit is accepted. In the default editor this occurs when Enter
is pressed while the text field has the focus.
Note: Unfortunately Swing does not provide an easily reusable ComboBoxEditor
implementation, forcing custom implementations to manage all ActionListener and item
selection/modification functionality from scratch (we hope to see this limitation accounted
for in a future Swing release).
UI Guideline : Advice on Usage and Design Usage
Comboboxes and List Boxes are very similar. In fact a Combobox is an Entry Field with a
drop down List Box. Deciding when to use one or another can be difficult. Our advice is to
think about reader output rather than data input. When the reader only needs to see a
single item then a Combobox is the choice. Use a Combobox where a single selection is
made from a collection and for reading purposes it is only necessary to see a single item,
e.g. Currency USD.
Design
There are a number of things which affect the usability of a combobox. Beyond more than a
4

few items, they become unusable unless the data is sorted in some logical fashion e.g.
alphabetical, numerical. When a list gets longer, usability is affected again.Once a list gets
beyond a couple of hundred items, even when sorted, it becomes very slow for the user to
locate specific item in the list. Some implementations have solved this by offering an ability
to type in partial text and the list "jumps" to the best match or partial match item e.g. type
in "ch" and the combobox will jump to "Chevrolet" as in the example in this chapter. You
may like to consider such an enhancement to a JCombobox to improve the usability in
longer lists.
There are a number of graphical considerations too. Like all other data entry fields,
comboboxes should be aligned to fit attractively into a panel. However, this can be
problematic. You must avoid making a combobox which is simply too big for the list items
contained e.g. a combobox for currency code ( typicall USD for U.S. Dollars ) only needs to
be 3 characters long. So don't make it big enough to take 50 characters. It will look
unbalanced. Another problem, is the nature of the list items. If you have 50 items in a list
where most items are around 20 characters but one item is 50 characters long then should
you make the combobox big enough to display the longer one? Well maybe but for most
occasions your display will be unbalanced again. It is probably best to optimise for the more
common length, providing the the longer one still has meaning when read in its truncated
form. One solution to displaying the whole length of a truncated item is to use the tooltip
facility. When the User places the mouse over an item, a tooltip appears with the full length
data.
One thing you must never do is dynamically resize the combobox to fit a varying length
item selection. This will provide alignment problems and may also add a usability problem
because the pull-down button may become a moving target which denies the user the
option to learn its position with directional memory.

9.2 Basic JComboBox example


This example displays information about popular cars in two symmetrical panels to
provide a natural means of comparison. To be more or less realistic, we need to take
into account that any car model comes in several trim lines which actually determine
the car's characteristics and price. Numerous characteristics of cars are available on the
web. For this simple example we've selected the following two-level data structure:
CAR
Name
Name
Manufacturer
Image
Trims
TRIM
Name
Name
MSRP
Invoice
Engine

Type
String
String
Icon
Vector

Description
Model's name
Company manufacturer
Model's photograph
A collection of model's trims

Type
Description
String
Trim's name
int
Manufacturer's suggested retail price
int
Invoice
price
String
Engine description

Figure 9.1 Dynamically changeable JComboBoxes allowing comparison of car model and trim
information.

<<file figure9-1.gif>>
The Code: ComboBox1.java
see \Chapter9\1
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importjavax.swing.*;
importjavax.swing.border.*;
importjavax.swing.event.*;
publicclassComboBox1extendsJFrame
{
publicComboBox1(){
super("ComboBoxes[CompareCars]");
getContentPane().setLayout(newBorderLayout());
Vectorcars=newVector();
Carmaxima=newCar("Maxima","Nissan",newImageIcon(
"maxima.gif"));
maxima.addTrim("GXE",21499,19658,"3.0LV6190hp");
maxima.addTrim("SE",23499,21118,"3.0LV6190hp");
maxima.addTrim("GLE",26899,24174,"3.0LV6190hp");
cars.addElement(maxima);
Caraccord=newCar("Accord","Honda",newImageIcon(
"accord.gif"));
accord.addTrim("LXSedan",21700,19303,"3.0LV6200hp");
accord.addTrim("EXSedan",24300,21614,"3.0LV6200hp");
cars.addElement(accord);
Carcamry=newCar("Camry","Toyota",newImageIcon(
"camry.gif"));
camry.addTrim("LEV6",21888,19163,"3.0LV6194hp");
camry.addTrim("XLEV6",24998,21884,"3.0LV6194hp");
cars.addElement(camry);
6

Carlumina=newCar("Lumina","Chevrolet",newImageIcon(
"lumina.gif"));
lumina.addTrim("LS",19920,18227,"3.1LV6160hp");
lumina.addTrim("LTZ",20360,18629,"3.8LV6200hp");
cars.addElement(lumina);
Cartaurus=newCar("Taurus","Ford",newImageIcon(
"taurus.gif"));
taurus.addTrim("LS",17445,16110,"3.0LV6145hp");
taurus.addTrim("SE",18445,16826,"3.0LV6145hp");
taurus.addTrim("SHO",29000,26220,"3.4LV8235hp");
cars.addElement(taurus);
Carpassat=newCar("Passat","Volkswagen",newImageIcon(
"passat.gif"));
passat.addTrim("GLSV6",23190,20855,"2.8LV6190hp");
passat.addTrim("GLX",26250,23589,"2.8LV6190hp");
cars.addElement(passat);

getContentPane().setLayout(newGridLayout(1,2,5,3));
CarPanelpl=newCarPanel("BaseModel",cars);
getContentPane().add(pl);
CarPanelpr=newCarPanel("Compareto",cars);
getContentPane().add(pr);
WindowListenerwndCloser=newWindowAdapter(){
publicvoidwindowClosing(WindowEvente){
System.exit(0);
}
};
addWindowListener(wndCloser);
pl.selectCar(maxima);
pr.selectCar(accord);
setResizable(false);
pack();
setVisible(true);
}
publicstaticvoidmain(Stringargv[]){
newComboBox1();
}
}
classCar
{
protectedStringm_name;
protectedStringm_manufacturer;
protectedIconm_img;
protectedVectorm_trims;
publicCar(Stringname,Stringmanufacturer,Iconimg){
m_name=name;
m_manufacturer=manufacturer;
m_img=img;
m_trims=newVector();
}
publicvoidaddTrim(Stringname,intMSRP,intinvoice,
Stringengine){
7

Trimtrim=newTrim(this,name,MSRP,invoice,engine);
m_trims.addElement(trim);
}
publicStringgetName(){returnm_name;}
publicStringgetManufacturer(){returnm_manufacturer;}
publicIcongetIcon(){returnm_img;}
publicVectorgetTrims(){returnm_trims;}
publicStringtoString(){returnm_manufacturer+""+m_name;}
}
classTrim
{
protectedCarm_parent;
protectedStringm_name;
protectedintm_MSRP;
protectedintm_invoice;
protectedStringm_engine;
publicTrim(Carparent,Stringname,intMSRP,intinvoice,
Stringengine){
m_parent=parent;
m_name=name;
m_MSRP=MSRP;
m_invoice=invoice;
m_engine=engine;
}
publicCargetCar(){returnm_parent;}
publicStringgetName(){returnm_name;}
publicintgetMSRP(){returnm_MSRP;}
publicintgetInvoice(){returnm_invoice;}
publicStringgetEngine(){returnm_engine;}
publicStringtoString(){returnm_name;}
}
classCarPanelextendsJPanel
{
protectedJComboBoxm_cbCars;
protectedJComboBoxm_cbTrims;
protectedJLabelm_lblImg;
protectedJLabelm_lblMSRP;
protectedJLabelm_lblInvoice;
protectedJLabelm_lblEngine;
publicCarPanel(Stringtitle,Vectorcars){
super();
setLayout(newBoxLayout(this,BoxLayout.Y_AXIS));
setBorder(newTitledBorder(newEtchedBorder(),title));
JPanelp=newJPanel();
p.add(newJLabel("Model:"));
8

m_cbCars=newJComboBox(cars);
ActionListenerlst=newActionListener(){
publicvoidactionPerformed(ActionEvente){
Carcar=(Car)m_cbCars.getSelectedItem();
if(car!=null)
showCar(car);
}
};
m_cbCars.addActionListener(lst);
p.add(m_cbCars);
add(p);
p=newJPanel();
p.add(newJLabel("Trim:"));
m_cbTrims=newJComboBox();
lst=newActionListener(){
publicvoidactionPerformed(ActionEvente){
Trimtrim=(Trim)m_cbTrims.getSelectedItem();
if(trim!=null)
showTrim(trim);
}
};
m_cbTrims.addActionListener(lst);
p.add(m_cbTrims);
add(p);
p=newJPanel();
m_lblImg=newJLabel();
m_lblImg.setHorizontalAlignment(JLabel.CENTER);
m_lblImg.setPreferredSize(newDimension(140,80));
m_lblImg.setBorder(newBevelBorder(BevelBorder.LOWERED));
p.add(m_lblImg);
add(p);
p=newJPanel();
p.setLayout(newGridLayout(3,2,10,5));
p.add(newJLabel("MSRP:"));
m_lblMSRP=newJLabel();
p.add(m_lblMSRP);
p.add(newJLabel("Invoice:"));
m_lblInvoice=newJLabel();
p.add(m_lblInvoice);
p.add(newJLabel("Engine:"));
m_lblEngine=newJLabel();
p.add(m_lblEngine);
add(p);
}
publicvoidselectCar(Carcar){m_cbCars.setSelectedItem(car);}
publicvoidshowCar(Carcar){
m_lblImg.setIcon(car.getIcon());
if(m_cbTrims.getItemCount()>0)
m_cbTrims.removeAllItems();
Vectorv=car.getTrims();
for(intk=0;k<v.size();k++)
m_cbTrims.addItem(v.elementAt(k));
m_cbTrims.grabFocus();
}

publicvoidshowTrim(Trimtrim){
m_lblMSRP.setText("$"+trim.getMSRP());
m_lblInvoice.setText("$"+trim.getInvoice());
m_lblEngine.setText(trim.getEngine());
}
}

Understanding the Code

Class ComboBox1
Class ComboBox1extends JFrame to implement the frame container for this example. It
has no instance variables. The constructor of the ComboBox1 class creates a data
collection with car information as listed above. A collection of cars is stored in Vector
cars, and each car, in turn, receives one or more Trim instances. Other than this, the
ComboBox1 constructor doesn't do much. It creates two instances of CarPanel (see
below) and arranges them in a GridLayout. These panels are used to select and display
car information. Finally two cars are initially selected in both panels.

Class Car
Car is a typical data object encapsulating three data fields listed at the beginning of this
section: car name, manufacturer, and image. In addition, it holds the m_trims vector
representing a collection of Trim instances.
Method addTrim() creates a new Trim instance and adds it to the m_trims vector. The
rest of this class implements typical getXX() methods to allow access to the protected
data fields.

Class Trim
Trim encapsulates four data fields listed at the beginning of this section: trim name,
suggested retail price, invoice price, and engine type. In addition, it holds a reference to
the parent Car instance. The rest of this class implements typical getXX() methods to
allow access to the protected data fields.

Class CarPanel
This class extends JPanel to provide the GUI framework for displaying car information.
Six components are declared as instance variables:
JComboBoxm_cbCars: Combo box to select a car model.
JComboBoxm_cbTrims: Combo box to select a car trim for the selected model.
JLabelm_lblImg: Label to display the model's image.
JLabelm_lblMSRP: Label to display the MSRP.
JLabelm_lblInvoice: Label to display the invoice price.
JLabelm_lblEngine: Label to display the engine description.

Two combo boxes are used to select cars and trims respectively. Note that Car and Trim
data objects are used to populate these combo boxes, so the actual displayed text is
determined by their toString() methods. Both combo boxes receive ActionListeners
to handle item selection. Then a Car item is selected, which triggers a call to the
showCar() method described below. Similarly, a selection of a Trim item triggers a call
to the showTrim() method.

10

The rest of the CarPanel constructor builds JLabels to display a car's image and trim
data. Note how layouts are used in this example. A y-oriented BoxLayout creates a
vertical axis used to allign and position all components. The combo boxes and
supplementary labels are encapsulated in horizontal JPanels. JLabel m_lblImg receives
a custom preferred size to reserve enough space for the photo image. This label is
encapsulated in a panel (with its default FlowLayout) to ensure that this component will
be centered over the container's space. The rest of CarPanel is occupied by the six
labels, which are hosted by a 3x2 GridLayout.
Method selectCar() allows us to select a car programmatically from outside this class.
It invokes the setSelectedItem() method on the m_cbCars combo box. Note that this
call will trigger an ActionEvent which will be captured by the proper listener, resulting
in a showCar()call.
Method showCar() updates the car image and updates the m_cbTrims combo box to
display the corresponding trims of the selected model. The (getItemCount() > 0)
condition is necessary because Swing throws an exception if removeAllItems() is
invoked on an empty JComboBox. Finally, focus is transferred to the m_cbTrims
component.
Method showTrim() updates the contents of the labels displaying trim information:
MSRP, invoice price, and engine type.
Running the Code
Figure 9.1 shows the ComboBox1 application displaying two cars simultaneously for
comparison. Note that all initial information is displayed correctly. Try experimenting
with various selections and note how the combo box contents change dynamically.
UI Guideline : Symmetrical Layout
In this example, the design avoids the problem of having to align the different length
comboboxes by using a symmetrical layout. Overall the window has a good balance and
good use of white space, as in turn do each of the bordered panes used for individual car
selections.

9.3 Custom model and renderer


Ambitious Swing developers may want to provide custom rendering in combo boxes to
display structured data in the drop-down list. Different levels of structure can be
identified by differing left margins and icons, just as is done in trees (which we will
study in chapter 17). Such complex combo boxes can enhance functionality and provide
a more sophisticated appearance.
In this section we will show how to merge the model and trim combo boxes from the
previous section into a single combo box. To differentiate between model and trim items
in the drop-down list, we can use different left margins and different icons for each. Our
list should looke something like this:
Nissan Maxima
GXE
SE
GLE
11

We also need to prevent the user from selecting models (e.g. Nissan Maxima above),
since they do not provide complete information about a specific car, and only serve as
separators between sets of trims.
Note: The hierarchical list organization shown here can easily be extended for use in a
JList, and can handle an arbitrary number of levels. We only use two levels in this
example, however, the design does not limit us to this.

Figure 9.2 JComboBox with a custom model and a custom hierarchical rendering scheme.

<<file figure9-2.gif>>
The Code: ComboBox2.java
see \Chapter9\2
//Unchangedcodefromsection9.2
classCarPanelextendsJPanel
{
protectedJComboBoxm_cbCars;
protectedJLabelm_txtModel;
protectedJLabelm_lblImg;
protectedJLabelm_lblMSRP;
protectedJLabelm_lblInvoice;
protectedJLabelm_lblEngine;
publicCarPanel(Stringtitle,Vectorcars){
super();
setLayout(newBoxLayout(this,BoxLayout.Y_AXIS));
setBorder(newTitledBorder(newEtchedBorder(),title));
JPanelp=newJPanel();
m_txtModel=newJLabel("");
m_txtModel.setForeground(Color.black);
p.add(m_txtModel);
add(p);
p=newJPanel();
12

p.add(newJLabel("Car:"));
CarComboBoxModelmodel=newCarComboBoxModel(cars);
m_cbCars=newJComboBox(model);
m_cbCars.setRenderer(newIconComboRenderer());
ActionListenerlst=newActionListener(){
publicvoidactionPerformed(ActionEvente){
ListDatadata=(ListData)m_cbCars.getSelectedItem();
Objectobj=data.getObject();
if(objinstanceofTrim)
showTrim((Trim)obj);
}
};
m_cbCars.addActionListener(lst);
p.add(m_cbCars);
add(p);
//Unchangedcodefromsection9.2
}
publicsynchronizedvoidselectCar(Carcar){
for(intk=0;k<m_cbCars.getItemCount();k++){
ListDataobj=(ListData)m_cbCars.getItemAt(k);
if(obj.getObject()==car){
m_cbCars.setSelectedItem(obj);
break;
}
}
}
publicsynchronizedvoidshowTrim(Trimtrim){
Carcar=trim.getCar();
m_txtModel.setText(car.toString());
m_lblImg.setIcon(car.getIcon());
m_lblMSRP.setText("$"+trim.getMSRP());
m_lblInvoice.setText("$"+trim.getInvoice());
m_lblEngine.setText(trim.getEngine());
}
}
classListData
{
protectedIconm_icon;
protectedintm_index;
protectedbooleanm_selectable;
protectedObjectm_data;
publicListData(Iconicon,intindex,booleanselectable,
Objectdata){
m_icon=icon;
m_index=index;
m_selectable=selectable;
m_data=data;
}
publicIcongetIcon(){returnm_icon;}
publicintgetIndex(){returnm_index;}
publicbooleanisSelectable(){returnm_selectable;}
publicObjectgetObject(){returnm_data;}
13

publicStringtoString(){returnm_data.toString();}
}
classCarComboBoxModelextendsDefaultComboBoxModel
{
publicstaticfinalImageIconICON_CAR=
newImageIcon("car.gif");
publicstaticfinalImageIconICON_TRIM=
newImageIcon("trim.gif");
publicCarComboBoxModel(Vectorcars){
for(intk=0;k<cars.size();k++){
Carcar=(Car)cars.elementAt(k);
addElement(newListData(ICON_CAR,0,false,car));
Vectorv=car.getTrims();
for(inti=0;i<v.size();i++){
Trimtrim=(Trim)v.elementAt(i);
addElement(newListData(ICON_TRIM,1,true,trim));
}
}
}
//Thismethodonlyallowstrimstobeselected
publicvoidsetSelectedItem(Objectitem){
if(iteminstanceofListData){
ListDataldata=(ListData)item;
if(!ldata.isSelectable()){
ObjectnewItem=null;
intindex=getIndexOf(item);
for(intk=index+1;k<getSize();k++){
Objectitem1=getElementAt(k);
if(item1instanceofListData){
ListDataldata1=(ListData)item1;
if(!ldata1.isSelectable())
continue;
}
newItem=item1;
break;
}
if(newItem==null)
return;//Selectionfailed
item=newItem;
}
}
super.setSelectedItem(item);
}
}
classIconComboRendererextendsJLabelimplementsListCellRenderer
{
publicstaticfinalintOFFSET=16;
protectedColorm_textSelectionColor=Color.white;
protectedColorm_textNonSelectionColor=Color.black;
protectedColorm_textNonselectableColor=Color.gray;
protectedColorm_bkSelectionColor=newColor(0,0,128);
protectedColorm_bkNonSelectionColor=Color.white;
protectedColorm_borderSelectionColor=Color.yellow;

14

protectedColorm_textColor;
protectedColorm_bkColor;
protectedbooleanm_hasFocus;
protectedBorder[]m_borders;
publicIconComboRenderer(){
super();
m_textColor=m_textNonSelectionColor;
m_bkColor=m_bkNonSelectionColor;
m_borders=newBorder[20];
for(intk=0;k<m_borders.length;k++)
m_borders[k]=newEmptyBorder(0,OFFSET*k,0,0);
setOpaque(false);
}
publicComponentgetListCellRendererComponent(JListlist,
Objectobj,introw,booleansel,booleanhasFocus){
if(obj==null)
returnthis;
setText(obj.toString());
booleanselectable=true;
if(objinstanceofListData){
ListDataldata=(ListData)obj;
selectable=ldata.isSelectable();
setIcon(ldata.getIcon());
intindex=0;
if(row>=0)//nooffsetforeditor(row=1)
index=ldata.getIndex();
Borderb=(index<m_borders.length?m_borders[index]:
newEmptyBorder(0,OFFSET*index,0,0));
setBorder(b);
}
else
setIcon(null);
setFont(list.getFont());
m_textColor=(sel?m_textSelectionColor:
(selectable?m_textNonSelectionColor:
m_textNonselectableColor));
m_bkColor=(sel?m_bkSelectionColor:
m_bkNonSelectionColor);
m_hasFocus=hasFocus;
returnthis;
}
publicvoidpaint(Graphicsg){
Iconicon=getIcon();
Borderb=getBorder();
g.setColor(m_bkNonSelectionColor);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(m_bkColor);
intoffset=0;
if(icon!=null&&getText()!=null){
Insetsins=getInsets();
offset=ins.left+icon.getIconWidth()+getIconTextGap();
}
g.fillRect(offset,0,getWidth()1offset,
getHeight()1);
15


if(m_hasFocus){
g.setColor(m_borderSelectionColor);
g.drawRect(offset,0,getWidth()1offset,getHeight()1);
}
setForeground(m_textColor);
setBackground(m_bkColor);
super.paint(g);
}
}

Understanding the Code

Class CarPanel
Classes ComboBox2 (formerly ComboBox1), Car, and Trim remain unchanged in this
example, so we'll start from the CarPanel class. Compared to the example in the
previous section, we've removed combo box m_cbTrims, and added JLabelm_txtModel,
which is used to display the current model's name (when the combo box popup is
hidden, the user can see only the selected trim; so we need to display the
corresponding model name separately). Curiously, the constructor of the CarPanel class
places this label component in its own JPanel (using its default FlowLayout) to ensure
it's location in the center of the base panel.
Note: The problem is that JLabelm_txtModel has a variable length, and the BoxLayout
which manages CarPanel cannot dynamically center this component correctly. By placing
this label in a FlowLayout panel it will always be centered.

The single combo box , m_cbCars, has a bit in common with the component of the same
name in the previous example. First it receives a custom model, an instance of the
CarComboBoxModel class, which will be described below. It also receives a custom
renderer, an instance of the IconComboRenderer class, also described below.
The combo box is populated by both Car and Trim instances encapsulated in ListData
objects (see below). This requires some changes in the actionPerformed() method
which handles combo box selection. First we extract the data object from the selected
ListData instance by calling the getObject() method. If this call returns a Trim object
(as it should, since Cars cannot be selected), we call the showTrim() method to display
the selected data.
Method selectCar() has been modified. As we mentioned above, our combo box now
holds ListData objects, so we cannot pass a Car object as a parameter to the
setSelectedItem() method. Instead we have to examine in turn all items in the combo
box, cast them to ListData objects, and verify that the encapsulated data object is
equal to the given Car instance. The == operator verifies that the address in memory of
the object corresponding to the combo box is the same as the address of the given
object. This assumes that the Car object passed to selectCar() is taken from the
collection of objects used to populate this combo box. (To avoid this limitation we could
alternatively implement an equals() method in the Car class.)
Method showTrim() now does the job of displaying the model data as well as the trim
data. To do this we obtain a parent Car instance for a given Trim and display the
model's name and icon. The rest of this method remains unchanged.
16

Class ListData
This class encapsulates the data object to be rendered in the combo box and adds new
attributes for our rendering needs.
Instance variables:
m_iconIcon: icon associated with the data object.
m_index int: item's index which determines the left margin (i.e. the hierarchical

level).
m_selectableboolean: flag indicating that this item can be selected.
m_dataObject: encapsulated data object.

All variables are filled with parameters passed to the constructor. The rest of the
ListData class represents four getXX() methods and a toString() method, which
delegate calls to the m_data object.

Class CarComboBoxModel
This class extends DefaultComboBoxModel to serve as a data model for our combo box .
First it creates two static ImageIcons to represent model and trim. The constructor takes
a Vector of Car instances and converts them and their trims into a linear sequence of
ListData objects. Each Car object is encapsulated in a ListData instance with an
ICON_CAR icon, index set to 0, and m_selectable flag set to false. Each Trim object is
encapsulated in a ListData instance with ICON_TRIM icon, index set to 1, and
m_selectable flag set to true.
These manipulations could have been done without implementing a custom
ComboBoxModel, of course. The real reason we do implement a custom model is to
override the setSelectedItem() method to control item selection in the combo box. As
we learned above, only ListData instances with the m_selectable flag set to true
should be selectable. To achieve this goal, the overridden setSelectedItem() method
casts the selected object to a ListData instance and examines its selection property
using isSelectable().
If isSelectable() returns false, a special action needs to be handled to move the
selection to the first item following this item for which isSelectable() returns true. If
no such item can be found our setSelectedItem() method returns and the selection in
the combo box remains unchanged. Otherwise the item variable receives a new value
which is finally passed to the setSelectedItem() implementation of the superclass
DefaultComboBoxModel.
Note: You may notice that the selectCar() method discussed above selects a Car instance
which cannot be selected. This internally triggers a call to the setSelectedItem() of the
combo box model, which shifts the selection to the first available Trim item. You can verify
this when running the example.

Class IconComboRenderer
This class extends JLabel and implements the ListCellRenderer interface to serve as a
custom combo box renderer.
Instance variables:
17

intOFFSET: offset in pixels of image and text (different for cars and trims).
Colorm_textColor: current text color.
Colorm_bkColor: current background color.
booleanm_hasFocus: flag indicating whether this item has focus.
Border[]m_borders: an array of borders used for this component.

The constructor of the IconComboRenderer class initializes these variables.


EmptyBorders are used to provide left margins while rendering components of the dropdown list. To avoid generation of numerous temporary objects, an array of 20 Borders is
prepared with increasing left offsets corresponding to array index (incremented by
OFFSET). This provides us with a set of different borders to use for white space in
representing data at 20 distinct hierarchical levels.
Note: Even though we only use two levels in this example, IconComboRenderer has been
designed for maximum reusability. 20 levels should be enough for most hierarchies, but if
more levels are necessary weve designed getListCellRendererComponent() (see below)
to create a new EmptyBorder in the event that more than 20 levels are used.

The opaque property is set to false because we intend to draw the background
ourselves.
Method getListCellRendererComponent()is called prior to the painting of each cell in
the drop-down list. We first set this components text to that of the given object (passed
as parameter). Then, if the object is an instance of ListData, we set the icon and left
margin by using the appropriate EmptyBorder from the previously prepared array
(based on the given ListDatas m_index property--if the index is greater than the). Note
that a call to this method with row=1 will be invoked prior to the rendering of the
combo box editor, which is the part of the combo box that is always visible (see 9.1). In
this case we don't need to use any border offset. Offset only makes sense when there
are hierarchical differences between items in the list, not when an item is rendered
alone.
The rest of the getListCellRendererComponent() method determines the background
and foreground colors to use, based on whether is selected and selectable, and stores
them in instance variables for use within the paint() method. Non-selectable items
receive their own foreground to distinguish them from selectable items.
The paint() method performs a bit of rendering before invoking the super-class
implementation. It fills the background with the stored m_bkColor (from above)
excluding the icon's area (note that the left margin is already taken into account by the
component's Border). It also draws a border-like rectangle if the component currently
has the focus. This method then ends with a call to its super-classs paint() method
which takes responsibility for painting the label text and icon
Running the Code
Figure 9.2 shows our hierarchical drop-down list in action. Note that models and trim
lines can be easily differentiated because of the varying icons and offsets. In addition,
models have a gray foreground to imply that they cannot be selected.
This implementation is more user-friendly than the previous example because it
18

displays all available data in a single drop-down list. Try selecting different trims and
note how this changes data for both the model and trim information labels. Try selecting
a model and note that it will result in the selection of the first trim of that model.
UI Guideline : Improved Usability
From a usability perspective the solution in fig 9.2 is an improvement over the one
presented in fig 9.1. By using a combobox with a hierarchical data model, the designer has
reduced the data entry to a single selection and has presented the information in an
accessible and logical manner which also produces a visually cleaner result.
Further improvements could be made here by sorting the hierarchical data. In this example
it would seem appropriate to sort in a two tiered fashion: alphabetically by manufacturer;
and alphabetically by model. Thus Toyota would come after Ford and Toyota Corolla would
come after Toyota Camry.
This is an excellent example of how the programmer can improve UI Design and Usability
by doing additional work to make the User's Goal easier to achieve.

9.4 Comboboxes with memory


In some situations it is desirable to use editable combo boxes which keep a historical
list of choices for future reuse. This conveniently allows the user to select a previous
choice rather than typing identical text. A typical example of an editable combo box
with memory can be found in find/replace dialogs in many modern applications.
Another example, familiar to almost every modern computer user, is provided in many
Internet browsers which use an editable URL combo box with history mechanism. These
combo boxes accumulate typed addresses so the user can easily return to any
previously visited site by selecting it from the drop-down list instead of manually typing
it in again.
The following example shows how to create a simple browser application using an
editable combo box with memory. It uses the serialization mechanism to save data
between program sessions, and the JEditorPane component (described in more detail
in chapters 11 and 19) to display non-editable HTML files.

19

Figure 9.3 JComboBox with memory of previously visited URLs.

<<file figure9-3.gif>>

The Code: Browser.java


see \Chapter9\3
importjava.awt.*;
importjava.awt.event.*;
importjava.io.*;
importjava.net.*;
importjavax.swing.*;
importjavax.swing.event.*;
importjavax.swing.text.*;
importjavax.swing.text.html.*;
publicclassBrowserextendsJFrame
{
protectedJEditorPanem_browser;
protectedMemComboBoxm_locator;
protectedAnimatedLabelm_runner;
publicBrowser(){
super("HTMLBrowser[ComboBoxwithMemory]");
setSize(500,300);

JPanelp=newJPanel();
p.setLayout(newBoxLayout(p,BoxLayout.X_AXIS));
p.add(newJLabel("Address"));
p.add(Box.createRigidArea(newDimension(10,1)));
m_locator=newMemComboBox();
m_locator.load("addresses.dat");
BrowserListenerlst=newBrowserListener();
m_locator.addActionListener(lst);
p.add(m_locator);
p.add(Box.createRigidArea(newDimension(10,1)));
20

m_runner=newAnimatedLabel("clock",8);
p.add(m_runner);
getContentPane().add(p,BorderLayout.NORTH);
m_browser=newJEditorPane();
m_browser.setEditable(false);
m_browser.addHyperlinkListener(lst);
JScrollPanesp=newJScrollPane();
sp.getViewport().add(m_browser);
getContentPane().add(sp,BorderLayout.CENTER);
WindowListenerwndCloser=newWindowAdapter(){
publicvoidwindowClosing(WindowEvente){
m_locator.save("addresses.dat");
System.exit(0);
}
};
addWindowListener(wndCloser);

setVisible(true);
m_locator.grabFocus();
}

classBrowserListenerimplementsActionListener,HyperlinkListener
{
publicvoidactionPerformed(ActionEventevt){
StringsUrl=(String)m_locator.getSelectedItem();
if(sUrl==null||sUrl.length()==0||
m_runner.getRunning())
return;
BrowserLoaderloader=newBrowserLoader(sUrl);
loader.start();
}
publicvoidhyperlinkUpdate(HyperlinkEvente){
URLurl=e.getURL();
if(url==null||m_runner.getRunning())
return;
BrowserLoaderloader=newBrowserLoader(url.toString());
loader.start();
}
}
classBrowserLoaderextendsThread
{
protectedStringm_sUrl;

publicBrowserLoader(StringsUrl){m_sUrl=sUrl;}
publicvoidrun(){
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
m_runner.setRunning(true);
try{
URLsource=newURL(m_sUrl);
m_browser.setPage(source);
m_locator.add(m_sUrl);
}
catch(Exceptione){
21

JOptionPane.showMessageDialog(Browser.this,
"Error:"+e.toString(),
"Warning",JOptionPane.WARNING_MESSAGE);
}
m_runner.setRunning(false);
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
publicstaticvoidmain(Stringargv[]){newBrowser();}
}
classMemComboBoxextendsJComboBox
{
publicstaticfinalintMAX_MEM_LEN=30;
publicMemComboBox(){
super();
setEditable(true);
}
publicvoidadd(Stringitem){
removeItem(item);
insertItemAt(item,0);
setSelectedItem(item);
if(getItemCount()>MAX_MEM_LEN)
removeItemAt(getItemCount()1);
}
publicvoidload(StringfName){
try{
if(getItemCount()>0)
removeAllItems();
Filef=newFile(fName);
if(!f.exists())
return;
FileInputStreamfStream=
newFileInputStream(f);
ObjectInputstream=
newObjectInputStream(fStream);
Objectobj=stream.readObject();
if(objinstanceofComboBoxModel)
setModel((ComboBoxModel)obj);
stream.close();
fStream.close();
}
catch(Exceptione){
e.printStackTrace();
System.err.println("Serializationerror:"+e.toString());
}
}
publicvoidsave(StringfName){
try{
FileOutputStreamfStream=
newFileOutputStream(fName);
ObjectOutputstream=
newObjectOutputStream(fStream);
stream.writeObject(getModel());
stream.flush();
stream.close();
22

fStream.close();
}
catch(Exceptione){
e.printStackTrace();
System.err.println("Serializationerror:"+e.toString());
}
}
}
classAnimatedLabelextendsJLabelimplementsRunnable
{
protectedIcon[]m_icons;
protectedintm_index=0;
protectedbooleanm_isRunning;
publicAnimatedLabel(StringgifName,intnumGifs){
m_icons=newIcon[numGifs];
for(intk=0;k<numGifs;k++)
m_icons[k]=newImageIcon(gifName+k+".gif");
setIcon(m_icons[0]);
Threadtr=newThread(this);
tr.setPriority(Thread.MAX_PRIORITY);
tr.start();
}
publicvoidsetRunning(booleanisRunning){
m_isRunning=isRunning;
}
publicbooleangetRunning(){returnm_isRunning;}
publicvoidrun(){
while(true){
if(m_isRunning){
m_index++;
if(m_index>=m_icons.length)
m_index=0;
setIcon(m_icons[m_index]);
Graphicsg=getGraphics();
m_icons[m_index].paintIcon(this,g,0,0);
}
else{
if(m_index>0){
m_index=0;
setIcon(m_icons[0]);
}
}
try{Thread.sleep(500);}catch(Exceptionex){}
}
}
}

Understanding the Code

Class Browser
This class extends JFrame to implement the frame container for our browser. Instance
variables:
JEditorPanem_browser: text component to parse and render HTML files.
23

MemComboBoxm_locator: combo box to enter/select URL address.


AnimatedLabel m_runner: traditional animated icon alive while the browser is

requesting a URL.
The constructor creates the custom combo box, m_locator, and an associated
explanatory label. Then it creates the m_runner icon and places all three components in
the northern region of our frames content pane. JEditorPanem_browser is created and
placed in a JScrollPane to provide scrolling capabilities. This is then added to the
center of the content pane.
Note that the WindowListener, as used in many previous examples to close the frame
and terminate execution, receives an additional function: it invokes our custom save()
method (see below) on our custom combo box component before destroying the frame.
This saves the list of visited URLs entered as a file called addresses.dat in the current
running directory.

Class Browser.BrowserListener
This inner class implements both the ActionListener and HyperlinkListener
interfaces to manage navigation to HTML pages. The actionPerformed() method is
invoked when the user selects a new item in the combo box . It verifies that the
selection is valid and the browser is not currently running (i.e. requesting a URL). If
these checks are passed it then creates and starts a new BrowserLoader instance (see
below) for the specified address.
Method hyperlinkUpdate() is invoked when the user clicks a hyperlink in the currently
loaded web page. This method also determines the selected URL address and starts a
new BrowserLoader to load it.

Class Browser.BrowserLoader
This inner class extends Thread to load web pages into the JEditorPane component. It
takes a URL address parameter in the constructor and stores it in a instance variable.
The run() method sets the mouse cursor to hourglass ( Cursor.WAIT_CURSOR) and starts
the animated icon to indicate that the browser is busy.
The core functionality of this thread is enclosed in its try/catch block. If an exception
occurs during processing of the requested URL, it is displayed in simple dialog message
box (we will learn discuss JOptionPane in chapter 14).
The actual job of retrieving, parsing, and rendering the web page is hidden in a single
call to the setPage() method. So why do we need to create this separate thread instead
of making that simple call, say, in BrowserListener? The reason is, as we discussed in
chapter 2, by creating separate threads to do potentially time-consuming operations we
avoid clogging up the event-dispatching thread.

Class MemComboBox
This class extends JComboBox to add a historical mechanism for this component. The
constructor creates an underlying JComboBox component and sets its editable property
to true.
The add() method adds a new text string to the beginning of the list. If this item is
already present in the list, it is removed from the old position. If the resulting list is
longer than the pre-defined maximum length then the last item in the list is truncated.
24

Method load() loads a previously stored ComboBoxModel from file addresses.dat


using the serialization mechanism. The significant portion of this method reads an
object from an ObjectInputStream and sets it as the ComboBoxModel. Note that any
possible exceptions are only printed to the standard output and purposefully do not
distract the user (since this serialization mechanism should be considered an optional
feature).
Similarly, the save() method serializes our combo boxs ComboBoxModel. Any possible
exceptions are, again, printed to standard output and do not distract the user.

Class AnimatedLabel
Surprisingly, Swing does not provide any special support for animated components, so
we have to create our own component for this purpose. This provides us with an
interesting example of using threads in Java.
Note: Animated GIFs are fully supported by ImageIcon (see chapter 5) but we want
complete control over each animated frame here.

AnimatedLabel extends JLabel and implements the Runnable interface. Instance


variables:
Icon[]m_icons: an array of images to be used for animation.
intm_index: index of the current image.
booleanm_isRunning: flag indicating whether the animation is running.
The constructor takes a common name of a series of GIF files containing images for
animation, and the number of those files. These images are loaded and stored into an
array. When all images are loaded a thread with maximum priority is created and
started to run this Runnable instance.
The setRunning() and getRunning() methods simply manage the m_isRunning flag.
In the run() method we cyclically increment the m_index variable and draw an image
from the m_icons array with the corresponding index, exactly as you would expect from
an animated image. This is done only when the m_isRunning flag is set to true.
Otherwise, the image with index 0 is displayed. After an image is painted,
AnimatedLabel yields control to other threads and sleeps for 500 ms.
The interesting thing about this component is that it runs in parallel with other threads
which do not necessary yield control explicitly. In our case the concurrent
BrowserLoader thread spends the main part of its time inside the setPage() method,
and our animated icon runs in a separate thread signaling to the user that something is
going on. This is made possible because this animated component is running in the
thread with the maximum priority. Of course, we should use such thread priority with
caution. In our case it is appropriate since our thread consumes only a small amount of
the processor's time and does yield control to the lesser-priority threads (when it
sleeps).
Note: As a good exercise try using threads with normal priority or Swing's Timer component
in this example. You will find that this doesn't work as expected: the animated icon does not
show any animation while the browser is running.
25

Running the Code


Figure 9.3 shows the Browser application displaying a web page. Note that the
animated icon comes to life when the browser requests a URL. Also note how the
combo box is populated with URL addresses as we navigate to different web pages.
Now quit the application and re-start it. Note that our addresses have been saved and
restored (by serializing the combo box model, as discussed above).
Note: HTML rendering functionality is not yet matured. Do not be surprised if your favorite
web page looks signigicantly different in our Swing-based browser. As a matter of fact even
the JavaSoft home page throws several exceptions while being displayed in this Swing
component. (These exceptions occur outside our code, during the JEditorPane rendering-this is why they are not caught and handled by our code.)
UI Guideline : Usage of a Memory Combobox
The example given here is a good usage for such a device. However, a memory combobox
will not always be appropriate. Remember the advice that usability of an unsorted
comboboxes tends to degrade rapidly as the number of items grows. Therefore, it is
sensible to deploy this technique where the likelihood of more than say 20 entries is very
small. The browser example is good because it is unlikely that a user would type more than
20 URLs in a single web surfing session.
Where you have a domain problem which is likely to need a larger number of memory items
but you still want to use a memory combobox, consider adding a sorting algorithm, so that
rather than most recent first, you sort into a meaningful index such as alphabetical order.
This will improve usability and mean that you could easily populate the list up to 2 or 3
hundred items.

9.5 Custom editing


In this section we will discuss a custom editing feature to make the example from the
last section even more convenient and similar to modern browser applications. We will
attach a key event listener to our combo boxs editor and search for previously viosited
URLs with matching beginning strings. If a match occurs the remainder of that URL is
displayed in the editor, and by pressing Enter we can accept the suggestion. Most
modern browsers also provide this functionality.
Note that the caret position will remain unchanged as well as the text on the left side of
the caret (i.e. the text most likely typed by the user). The text on the right side of the
caret represents the browser's suggestion which may or may not correspond to the
user's intentions. To avoid distracting the user, this portion of the text is highlighted, so
any newly typed character will replace that suggested text.

26

Figure 9.4 JComboBox with custom editor suggesting previously visited URLs.

<<file figure9-4.gif>>

The Code: Browser.java


see \Chapter9\4
publicclassBrowserextendsJFrame
{
//Unchangedcodefromsection9.4
publicBrowser(){
super("HTMLBrowser[AdvancedEditor]");
//Unchangedcodefromsection9.4
MemComboAgentagent=newMemComboAgent(m_locator);
//Unchangedcodefromsection9.4
}
//Unchangedcodefromsection9.4
}
classMemComboAgentextendsKeyAdapter
{
protectedJComboBoxm_comboBox;
protectedJTextFieldm_editor;
publicMemComboAgent(JComboBoxcomboBox){
m_comboBox=comboBox;
m_editor=(JTextField)comboBox.getEditor().
getEditorComponent();
m_editor.addKeyListener(this);
}
publicvoidkeyReleased(KeyEvente){
charch=e.getKeyChar();
if(ch==KeyEvent.CHAR_UNDEFINED||Character.isISOControl(ch))
27

return;
intpos=m_editor.getCaretPosition();
Stringstr=m_editor.getText();
if(str.length()==0)
return;
for(intk=0;k<m_comboBox.getItemCount();k++){
Stringitem=m_comboBox.getItemAt(k).toString();
if(item.startsWith(str)){
m_editor.setText(item);
m_editor.setCaretPosition(item.length());
m_editor.moveCaretPosition(pos);
break;
}
}
}
}

Understanding the Code

Class Browser
This class has only one change in comparison with the previous example: it creates an
instance of our custom MemComboAgent class and passes it a reference to our
m_locator combo box.

Class MemComboAgent
This class extends KeyAdapter to listen for keyboard activity. It takes a reference to a
JComboBox component and stores it in an instance variable along with the JTextField
component used as that combo boxs editor. Finally, a MemComboAgent object adds itself
to that editor as a KeyListener to be notified of all keyboard input that is passed to the
editor component.
Method keyReleased() is the only method we implement. First this method retrieves
the pressed characters and verifies that they are not control characters. We also
retrieve the contents of the text field and check that it is not empty (to avoid annoying
the user with suggestions in an empty field). Note that when this method is invoked the
pressed key will already have been included in this text.
This method then walks through the list of combo box items and searches for an item
starting with the combo box editor text. If such an item is found it is set as the combo
box editors text. Then we place the caret at the end of that string using
setCaretPosition(), and move it back to its initial position in the backward direction
using the moveCaretPosition() method. This final JTextComponent method places the
caret in its original position and highlights all text to its right (see chapters 11 and 19).
Note: A more sophisticated realization of this idea may include separate processing of URL
protocol and host, as well as using threads for smooth execution.

Running the Code


Figure 9.4 shows our custom combo boxs editor displaying a portion of a URL address
taken from its list. Try entering some new addresses and browsing to them. After some
experimentation, try typing in an address that you have already visited with this
application. Notice that the enhanced combo box suggests the remainder of this
address from its pull-down list. Press "Enter" as soon as an address matches your
28

intended selection to avoid typing the complete URL.

29

You might also like