Introduction
to Swing
[60]
I suggest placing the superscript after Swing in “Introduction to
Swing.”After working your way through this chapter and seeing the huge
changes that have occurred within the AWT (although, if you can remember back
that far, Sun claimed Java was a “stable” language when it first
appeared), you might still have the feeling that it’s not quite done.
Sure, there’s now a good event model, and JavaBeans is an excellent
component-reuse design. But the GUI components still seem rather minimal,
primitive, and awkward.
That’s
where Swing
comes in. The Swing library appeared after Java 1.1 so you might naturally
assume that it’s part of Java
1.2. However, it is designed to work with Java
1.1 as an add-on. This way, you don’t have to wait for your platform to
support Java 1.2 in order to enjoy a good UI component library. Your users
might actually need to download the Swing library if it isn’t part of
their Java 1.1 support, and this could cause a few snags. But it works.
Swing
contains all the components that you’ve been missing throughout the rest
of this chapter: those you expect to see in a modern UI, everything from
buttons that contain pictures to trees and grids. It’s a big library, but
it’s designed to have appropriate complexity for the task at hand –
if something is simple, you don’t have to write much code but as you try
to do more your code becomes increasingly complex. This means an easy entry
point, but you’ve got the power if you need it.
Swing
has great depth. This section does not attempt to be comprehensive, but instead
introduces the power and simplicity of Swing to get you started using the
library. Please be aware that what you see here is intended to be simple. If
you need to do more, then Swing can probably give you what you want if
you’re willing to do the research by hunting through the online
documentation from Sun.
Benefits
of Swing
When
you begin to use the Swing library, you’ll see that it’s a huge
step forward. Swing components are Beans (and thus use the Java 1.1 event
model), so they can be used in any development environment that supports Beans.
Swing provides a full set of UI components. For speed, all the components
are lightweight (no “peer” components are used), and Swing is
written entirely in Java for portability.
Much
of what you’ll like about Swing could be called “orthogonality of
use;” that is, once you pick up the general ideas about the library you
can apply them everywhere. Primarily because of the Beans naming conventions,
much of the time I was writing these examples I could guess at the method names
and get it right the first time, without looking anything up. This is certainly
the hallmark of a good library design. In addition, you can generally plug
components into other components and things will work correctly.
Keyboard
navigation is automatic – you can use a Swing application without the
mouse, but you don’t have to do any extra programming (the old AWT
required some ugly code to achieve keyboard navigation). Scrolling support is
effortless – you simply wrap your component in a JScrollPane
as you add it to your form. Other features such as tool tips typically require
a single line of code to implement.
Swing
also supports something called “pluggable look and feel,” which
means that the appearance of the UI can be dynamically changed to suit the
expectations of users working under different platforms and operating systems.
It’s even possible to invent your own look and feel.
Easy
conversion
If
you’ve struggled long and hard to build your UI using Java 1.1, you
don’t want to throw it away to convert to Swing. Fortunately, the library
is designed to allow easy conversion – in many cases you can simply put a
‘J’ in front of the class names of each of your old AWT components.
Here’s an example that should have a familiar flavor to it:
//: JButtonDemo.java
// Looks like Java 1.1 but with J's added
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import com.sun.java.swing.*;
public class JButtonDemo extends Applet {
JButton
b1 = new JButton("JButton 1"),
b2 = new JButton("JButton 2");
JTextField t = new JTextField(20);
public void init() {
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e){
String name =
((JButton)e.getSource()).getText();
t.setText(name + " Pressed");
}
};
b1.addActionListener(al);
add(b1);
b2.addActionListener(al);
add(b2);
add(t);
}
public static void main(String args[]) {
JButtonDemo applet = new JButtonDemo();
JFrame frame = new JFrame("TextAreaNew");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
frame.getContentPane().add(
applet, BorderLayout.CENTER);
frame.setSize(300,100);
applet.init();
applet.start();
frame.setVisible(true);
}
} ///:~
There’s
a new
import
statement, but everything else looks like the Java 1.1 AWT with the addition of
some J’s. Also, you don’t just
add( )
something
to a Swing
JFrame,
but you must get the “content pane” first, as seen above.
But
you can easily get many of the benefits of Swing with a simple conversion.
Because
of the
package
statement, you’ll have to invoke this program by saying:
java
c13.swing.JbuttonDemo
All
of the programs in this section will require a similar form to run them.
A
display framework
Although
the programs that are both applets and applications can be valuable, if used
everywhere they become distracting and waste paper. Instead, a display
framework will be used for the Swing examples in the rest of this section:
//: Show.java
// Tool for displaying Swing demos
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
public class Show {
public static void
inFrame(JPanel jp, int width, int height) {
String title = jp.getClass().toString();
// Remove the word "class":
if(title.indexOf("class") != -1)
title = title.substring(6);
JFrame frame = new JFrame(title);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
frame.getContentPane().add(
jp, BorderLayout.CENTER);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~
Classes
that want to display themselves should inherit from
JPanel
and then add any visual components to themselves. Finally, they create a
main( )
containing the line:
Show.inFrame(new
MyClass(), 500, 300);
in
which the last two arguments are the display width and height.
Note
that the title for the JFrame
is produced using RTTI.
Tool
tips
Almost
all of the classes that you’ll be using to create your user interfaces
are derived from JComponent,
which contains a method called setToolTipText(String).
So, for virtually anything you place on your form, all you need to do is say
(for an object
jc
of
any
JComponent-derived
class):
jc.setToolTipText("My
tip");
and
when the mouse stays over that
JComponent
for a predetermined period of time, a tiny box containing your text will pop up
next to the mouse.
Borders
JComponent
also contains a method called setBorder( ),
which allows you to place various interesting borders on any visible component.
The following example demonstrates a number of the different borders that are
available, using a method called
showBorder( )
that creates a
JPanel
and puts on the border in each case. Also, it uses RTTI to find the name of the
border that you’re using (stripping off all the path information), then
puts that name in a JLabel
in the middle of the panel:
//: Borders.java
// Different Swing borders
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
public class Borders extends JPanel {
static JPanel showBorder(Border b) {
JPanel jp = new JPanel();
jp.setLayout(new BorderLayout());
String nm = b.getClass().toString();
nm = nm.substring(nm.lastIndexOf('.') + 1);
jp.add(new JLabel(nm, JLabel.CENTER),
BorderLayout.CENTER);
jp.setBorder(b);
return jp;
}
public Borders() {
setLayout(new GridLayout(2,4));
add(showBorder(new TitledBorder("Title")));
add(showBorder(new EtchedBorder()));
add(showBorder(new LineBorder(Color.blue)));
add(showBorder(
new MatteBorder(5,5,30,30,Color.green)));
add(showBorder(
new BevelBorder(BevelBorder.RAISED)));
add(showBorder(
new SoftBevelBorder(BevelBorder.LOWERED)));
add(showBorder(new CompoundBorder(
new EtchedBorder(),
new LineBorder(Color.red))));
}
public static void main(String args[]) {
Show.inFrame(new Borders(), 500, 300);
}
} ///:~
Most
of the examples in this section use TitledBorder,
but you can see that the rest of the borders are as easy to use. You can also
create your own borders and put them inside buttons, labels, etc. –
anything derived from
JComponent.
Buttons
Swing
adds a number of different types of buttons, and it also changes the
organization of the selection components: all buttons, checkboxes, radio
buttons, and even menu items are inherited from AbstractButton
(which, since menu items are included, would probably have been better named
“AbstractChooser” or something equally general). You’ll see
the use of menu items shortly, but the following example shows the various
types of buttons available:
//: Buttons.java
// Various Swing buttons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.basic.*;
import com.sun.java.swing.border.*;
public class Buttons extends JPanel {
JButton jb = new JButton("JButton");
BasicArrowButton
up = new BasicArrowButton(
BasicArrowButton.NORTH),
down = new BasicArrowButton(
BasicArrowButton.SOUTH),
right = new BasicArrowButton(
BasicArrowButton.EAST),
left = new BasicArrowButton(
BasicArrowButton.WEST);
Spinner spin = new Spinner(47, "");
StringSpinner stringSpin =
new StringSpinner(3, "",
new String[] {
"red", "green", "blue", "yellow" });
public Buttons() {
add(jb);
add(new JToggleButton("JToggleButton"));
add(new JCheckBox("JCheckBox"));
add(new JRadioButton("JRadioButton"));
up.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
spin.setValue(spin.getValue() + 1);
}
});
down.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
spin.setValue(spin.getValue() - 1);
}
});
JPanel jp = new JPanel();
jp.add(spin);
jp.add(up);
jp.add(down);
jp.setBorder(new TitledBorder("Spinner"));
add(jp);
left.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
stringSpin.setValue(
stringSpin.getValue() + 1);
}
});
right.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
stringSpin.setValue(
stringSpin.getValue() - 1);
}
});
jp = new JPanel();
jp.add(stringSpin);
jp.add(left);
jp.add(right);
jp.setBorder(
new TitledBorder("StringSpinner"));
add(jp);
}
public static void main(String args[]) {
Show.inFrame(new Buttons(), 300, 200);
}
} ///:~
The
JButton
looks like the AWT button, but there’s more you can do to it (like add
images, as you’ll see later). In
com.sun.java.swing.basic,
there is a BasicArrowButton
that is convenient, but what to test it on? There are two types of
“spinners” that just beg to be used with arrow buttons: Spinner,
which changes an
int
value, and StringSpinner,
which moves through an array of
String
(even automatically wrapping when it reaches the end of the array). The
ActionListeners
attached to the arrow buttons
shows
how relatively obvious it is to use these spinners: you just get and set
values, using method names you would expect since they’re Beans.
When
you run the example, you’ll see that the toggle button holds its last
position, in or out. But the check boxes and radio buttons behave identically
to each other, just clicking on or off (they are inherited from JToggleButton).
Button
groups
If
you want radio buttons to behave in an “exclusive or” fashion, you
must add them to a button group, in a similar but less awkward way as the old
AWT. But as the example below demonstrates, any
AbstractButton
can be added to a ButtonGroup. To
avoid repeating a lot of code, this example uses reflection to generate the
groups of different types of buttons. This is seen in
makeBPanel,
which creates a button group and a
JPanel,
and for each
String
in the array that’s the second argument to
makeBPanel( ),
it adds an object of the class represented by the first argument:
//: ButtonGroups.java
// Uses reflection to create groups of different
// types of AbstractButton.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import java.lang.reflect.*;
public class ButtonGroups extends JPanel {
static String[] ids = {
"June", "Ward", "Beaver",
"Wally", "Eddie", "Lumpy",
};
static JPanel
makeBPanel(Class bClass, String[] ids) {
ButtonGroup bg = new ButtonGroup();
JPanel jp = new JPanel();
String title = bClass.getName();
title = title.substring(
title.lastIndexOf('.') + 1);
jp.setBorder(new TitledBorder(title));
for(int i = 0; i < ids.length; i++) {
AbstractButton ab = new JButton("failed");
try {
// Get the dynamic constructor method
// that takes a String argument:
Constructor ctor = bClass.getConstructor(
new Class[] { String.class });
// Create a new object:
ab = (AbstractButton)ctor.newInstance(
new Object[]{ids[i]});
} catch(Exception ex) {
System.out.println("can't create " +
bClass);
}
bg.add(ab);
jp.add(ab);
}
return jp;
}
public ButtonGroups() {
add(makeBPanel(JButton.class, ids));
add(makeBPanel(JToggleButton.class, ids));
add(makeBPanel(JCheckBox.class, ids));
add(makeBPanel(JRadioButton.class, ids));
}
public static void main(String args[]) {
Show.inFrame(new ButtonGroups(), 500, 300);
}
} ///:~
The
title for the border is taken from the name of the class, stripping off all the
path information. The
AbstractButton
is initialized to a
JButton
that has the label “Failed” so if you ignore the exception message,
you’ll still see the problem on screen. The getConstructor( )
method produces a Constructor
object that takes the array of arguments of the types in the Class
array
passed to
getConstructor( ).
Then all you do is call newInstance( ),
passing it an array of
Object
containing your actual arguments – in this case, just the
String
from the
ids
array.
This
adds a little complexity to what is a simple process. To get “exclusive
or” behavior with buttons, you create a button group and add each button
for which you want that behavior to the group. When you run the program,
you’ll see that all the buttons except
JButton
exhibit this “exclusive or” behavior.
Icons
You
can use an Icon
inside a
JLabel
or anything that inherits from
AbstractButton
(including
JButton,
JCheckbox,
JradioButton,
and the different kinds of JMenuItem).
Using
Icons
with
JLabels
is quite straightforward (you’ll see an example later). The following
example explores all the additional ways you can use
Icons
with buttons and their descendants.
You
can use any
gif
files you want, but the ones used in this example are part of the book’s
code distribution, available at
www.BruceEckel.com.
To open a file and bring in the image, simply create an ImageIcon
and hand it the file name. From then on, you can use the resulting
Icon
in your program.
//: Faces.java
// Icon behavior in JButtons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
public class Faces extends JPanel {
static Icon[] faces = {
new ImageIcon("face0.gif"),
new ImageIcon("face1.gif"),
new ImageIcon("face2.gif"),
new ImageIcon("face3.gif"),
new ImageIcon("face4.gif"),
};
JButton
jb = new JButton("JButton", faces[3]),
jb2 = new JButton("Disable");
boolean mad = false;
public Faces() {
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if(mad) {
jb.setIcon(faces[3]);
mad = false;
} else {
jb.setIcon(faces[0]);
mad = true;
}
jb.setVerticalAlignment(JButton.TOP);
jb.setHorizontalAlignment(JButton.LEFT);
}
});
jb.setRolloverEnabled(true);
jb.setRolloverIcon(faces[1]);
jb.setPressedIcon(faces[2]);
jb.setDisabledIcon(faces[4]);
jb.setToolTipText("Yow!");
add(jb);
jb2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if(jb.isEnabled()) {
jb.setEnabled(false);
jb2.setText("Enable");
} else {
jb.setEnabled(true);
jb2.setText("Disable");
}
}
});
add(jb2);
}
public static void main(String args[]) {
Show.inFrame(new Faces(), 300, 200);
}
} ///:~
An
Icon
can be used in many constructors, but you can also use setIcon( )
to add or change an
Icon.
This example also shows how a
JButton
(or any
AbstractButton)
can set the various different sorts of icons that appear when things happen to
that button: when it’s pressed, disabled, or “rolled over”
(the mouse moves over it without clicking). You’ll see that this gives
the button a rather animated feel.
Note
that a tool
tip is also added to the button.
Menus
Menus
are much improved and more flexible in Swing – for example, you can use
them just about anywhere, including panels and applets. The syntax for using
them is much the same as it was in the old AWT, and this preserves the same
problem present in the old AWT: you must hard-code your menus and there
isn’t any support for menus as resources (which, among other things,
would make them easier to change for other languages). In addition, menu code
gets long-winded and sometimes messy. The following approach takes a step in
the direction of solving this problem by putting all the information about each
menu into a two-dimensional array
of
Object
(that way you can put anything you want into the array). This array is
organized so that the first row represents the menu name, and the remaining
rows represent the menu items and their characteristics. You’ll notice
the rows of the array do not have to be uniform from one to the next – as
long as your code knows where everything should be, each row can be completely
different.
//: Menus.java
// A menu-building system; also demonstrates
// icons in labels and menu items.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
public class Menus extends JPanel {
static final Boolean
bT = new Boolean(true),
bF = new Boolean(false);
// Dummy class to create type identifiers:
static class MType { MType(int i) {} };
static final MType
mi = new MType(1), // Normal menu item
cb = new MType(2), // Checkbox menu item
rb = new MType(3); // Radio button menu item
JTextField t = new JTextField(10);
JLabel l = new JLabel("Icon Selected",
Faces.faces[0], JLabel.CENTER);
ActionListener a1 = new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.setText(
((JMenuItem)e.getSource()).getText());
}
};
ActionListener a2 = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuItem mi = (JMenuItem)e.getSource();
l.setText(mi.getText());
l.setIcon(mi.getIcon());
}
};
// Store menu data as "resources":
public Object[][] fileMenu = {
// Menu name and accelerator:
{ "File", new Character('F') },
// Name type accel listener enabled
{ "New", mi, new Character('N'), a1, bT },
{ "Open", mi, new Character('O'), a1, bT },
{ "Save", mi, new Character('S'), a1, bF },
{ "Save As", mi, new Character('A'), a1, bF},
{ null }, // Separator
{ "Exit", mi, new Character('x'), a1, bT },
};
public Object[][] editMenu = {
// Menu name:
{ "Edit", new Character('E') },
// Name type accel listener enabled
{ "Cut", mi, new Character('t'), a1, bT },
{ "Copy", mi, new Character('C'), a1, bT },
{ "Paste", mi, new Character('P'), a1, bT },
{ null }, // Separator
{ "Select All", mi,new Character('l'),a1,bT},
};
public Object[][] helpMenu = {
// Menu name:
{ "Help", new Character('H') },
// Name type accel listener enabled
{ "Index", mi, new Character('I'), a1, bT },
{ "Using help", mi,new Character('U'),a1,bT},
{ null }, // Separator
{ "About", mi, new Character('t'), a1, bT },
};
public Object[][] optionMenu = {
// Menu name:
{ "Options", new Character('O') },
// Name type accel listener enabled
{ "Option 1", cb, new Character('1'), a1,bT},
{ "Option 2", cb, new Character('2'), a1,bT},
};
public Object[][] faceMenu = {
// Menu name:
{ "Faces", new Character('a') },
// Optinal last element is icon
{ "Face 0", rb, new Character('0'), a2, bT,
Faces.faces[0] },
{ "Face 1", rb, new Character('1'), a2, bT,
Faces.faces[1] },
{ "Face 2", rb, new Character('2'), a2, bT,
Faces.faces[2] },
{ "Face 3", rb, new Character('3'), a2, bT,
Faces.faces[3] },
{ "Face 4", rb, new Character('4'), a2, bT,
Faces.faces[4] },
};
public Object[] menuBar = {
fileMenu, editMenu, faceMenu,
optionMenu, helpMenu,
};
static public JMenuBar
createMenuBar(Object[] menuBarData) {
JMenuBar menuBar = new JMenuBar();
for(int i = 0; i < menuBarData.length; i++)
menuBar.add(
createMenu((Object[][])menuBarData[i]));
return menuBar;
}
static ButtonGroup bgroup;
static public JMenu
createMenu(Object[][] menuData) {
JMenu menu = new JMenu();
menu.setText((String)menuData[0][0]);
menu.setKeyAccelerator(
((Character)menuData[0][1]).charValue());
// Create redundantly, in case there are
// any radio buttons:
bgroup = new ButtonGroup();
for(int i = 1; i < menuData.length; i++) {
if(menuData[i][0] == null)
menu.add(new JSeparator());
else
menu.add(createMenuItem(menuData[i]));
}
return menu;
}
static public JMenuItem
createMenuItem(Object[] data) {
JMenuItem m = null;
MType type = (MType)data[1];
if(type == mi)
m = new JMenuItem();
else if(type == cb)
m = new JCheckBoxMenuItem();
else if(type == rb) {
m = new JRadioButtonMenuItem();
bgroup.add(m);
}
m.setText((String)data[0]);
m.setKeyAccelerator(
((Character)data[2]).charValue());
m.addActionListener(
(ActionListener)data[3]);
m.setEnabled(
((Boolean)data[4]).booleanValue());
if(data.length == 6)
m.setIcon((Icon)data[5]);
return m;
}
Menus() {
setLayout(new BorderLayout());
add(createMenuBar(menuBar),
BorderLayout.NORTH);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
p.add(t, BorderLayout.NORTH);
p.add(l, BorderLayout.CENTER);
add(p, BorderLayout.CENTER);
}
public static void main(String args[]) {
Show.inFrame(new Menus(), 300, 200);
}
} ///:~
The
goal is to allow the programmer to simply create tables to represent each menu,
rather than typing lines of code to build the menus. Each table produces one
menu, and the first row in the table contains the menu name and its keyboard
accelerator. The remaining rows contain the data for each menu item: the string
to be placed on the menu item, what type of menu item it is, its keyboard
accelerator, the actionlistener that is fired when this menu item is selected,
and whether this menu item is enabled. If a row starts with
null
it is treated as a separator.
To
prevent wasteful and tedious multiple creations of
Boolean
objects and type flags, these are created as
static
final
values at the beginning of the class:
bT
and
bF
to represent
Booleans
and different objects of the dummy class
MType
to describe normal menu items (
mi),
checkbox menu items (
cb),
and radio button menu items (
rb).
Remember that an array of
Object
may hold only
Object
handles and not primitive values.
This
example also shows how JLabels
and JMenuItems
(and their descendants) may hold
Icons.
An
Icon
is
placed into the
JLabel
via its constructor and changed when the corresponding menu item is selected.
The
menuBar
array contains the handles to all the file menus in the order that you want
them to appear on the menu bar. You pass this array to
createMenuBar( ),
which breaks it up into individual arrays of menu data, passing each to
createMenu( ).
This method, in turn, takes the first line of the menu data and creates a JMenu
from it, then calls
createMenuItem( )
for each of the remaining lines of menu data. Finally,
createMenuItem( )
parses each line of menu data and determines the type of menu and its
attributes, and creates that menu item appropriately. In the end, as you can
see in the
Menus( )
constructor, to create a menu from these tables say
createMenuBar(menuBar)
and everything is handled recursively.
This
example does not take care of building cascading menus, but you should have
enough of the concept that you can add that capability if you need it.
Popup
menus
The
implementation of JPopupMenu
seems a bit strange: you must call enableEvents( )
and select for mouse events instead of using an event listener. That is,
it’s possible to add a mouse listener but the MouseEvent
that comes through doesn’t return
true
from isPopupTrigger( )
– it doesn’t know that it should trigger a popup menu.
[61]
In addition, when I tried the listener approach it behaved strangely, possibly
from recursive click handling. In any event, the following example produces the
desired popup behavior:
//: Popup.java
// Creating popup menus with Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
public class Popup extends JPanel {
JPopupMenu popup = new JPopupMenu();
JTextField t = new JTextField(10);
public Popup() {
add(t);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e){
t.setText(
((JMenuItem)e.getSource()).getText());
}
};
JMenuItem m = new JMenuItem("Hither");
m.addActionListener(al);
popup.add(m);
m = new JMenuItem("Yon");
m.addActionListener(al);
popup.add(m);
m = new JMenuItem("Afar");
m.addActionListener(al);
popup.add(m);
popup.addSeparator();
m = new JMenuItem("Stay Here");
m.addActionListener(al);
popup.add(m);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
protected void processMouseEvent(MouseEvent e){
if (e.isPopupTrigger())
popup.show(
e.getComponent(), e.getX(), e.getY());
super.processMouseEvent(e);
}
public static void main(String args[]) {
Show.inFrame(new Popup(),200,150);
}
} ///:~
The
same
ActionListener
is added to each JMenuItem,
so that it fetches the text from the menu label and inserts it into the JTextField.
List
boxes and combo boxes
List
boxes and combo
boxes in Swing work much as they do in the old AWT, but they also have
increased functionality if you need it. In addition, some conveniences have
been added. For example, the JList
has a constructor that takes an array of
Strings
to display (oddly enough this same feature is not available in JComboBox).
Here’s a simple example that shows the basic use of each:
//: ListCombo.java
// List boxes & Combo boxes
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
public class ListCombo extends JPanel {
public ListCombo() {
setLayout(new GridLayout(2,1));
JList list = new JList(ButtonGroups.ids);
add(new JScrollPane(list));
JComboBox combo = new JComboBox();
for(int i = 0; i < 100; i++)
combo.addItem(Integer.toString(i));
add(combo);
}
public static void main(String args[]) {
Show.inFrame(new ListCombo(),200,200);
}
} ///:~
Something
else that seems a bit odd at first is that
JLists
do not automatically provide scrolling, even though that’s something you
always expect. Adding support for scrolling turns out to be quite easy, as
shown above – you simply wrap the
JList
in a JScrollPane
and all the details are automatically managed for you.
Sliders
and progress bars
A
slider
allows the user to input data by moving a point back and forth, which is
intuitive in some situations (volume controls, for example). A progress
bar displays data in a relative fashion from “full” to
“empty” so the user gets a perspective. My favorite example for
these is to simply hook the slider to the progress bar so when you move the
slider the progress bar changes accordingly:
//: Progress.java
// Using progress bars and sliders
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
import com.sun.java.swing.border.*;
public class Progress extends JPanel {
JProgressBar pb = new JProgressBar();
JSlider sb =
new JSlider(JSlider.HORIZONTAL, 0, 100, 60);
public Progress() {
setLayout(new GridLayout(2,1));
add(pb);
sb.setValue(0);
sb.setPaintTicks(true);
sb.setMajorTickSpacing(20);
sb.setMinorTickSpacing(5);
sb.setBorder(new TitledBorder("Slide Me"));
sb.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
pb.setValue(sb.getValue());
}
});
add(sb);
}
public static void main(String args[]) {
Show.inFrame(new Progress(),200,150);
}
} ///:~
The
JProgressBar
is
fairly straightforward, but the JSlider
has a lot of options, such as the orientation and major and minor tick marks.
Notice how straightforward it is to add a titled border.
Trees
Using
a JTree
can
be as simple as saying:
add(new JTree(
new Object[] {"this", "that", "other"}));
This
displays a primitive tree.
The API for trees is vast, however – certainly one of the largest in
Swing. It appears that you can do just about anything with trees, but more
sophisticated tasks might require quite a bit of research and experimentation.
Fortunately,
there is a middle ground provided in the library: the “default”
tree components, which generally do what you need. So most of the time you can
use these components, and only in special cases will you need to delve in and
understand trees more deeply.
The
following example uses the “default” tree components to display a
tree in an applet. When you press the button, a new subtree is added under the
currently-selected node (if no node is selected, the root node is used):
//: Trees.java
// Simple Swing tree example. Trees can be made
// vastly more complex than this.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.tree.*;
// Takes an array of Strings and makes the first
// element a node and the rest leaves:
class Branch {
DefaultMutableTreeNode r;
public Branch(String[] data) {
r = new DefaultMutableTreeNode(data[0]);
for(int i = 1; i < data.length; i++)
r.add(new DefaultMutableTreeNode(data[i]));
}
public DefaultMutableTreeNode node() {
return r;
}
}
public class Trees extends JPanel {
String[][] data = {
{ "Colors", "Red", "Blue", "Green" },
{ "Flavors", "Tart", "Sweet", "Bland" },
{ "Length", "Short", "Medium", "Long" },
{ "Volume", "High", "Medium", "Low" },
{ "Temperature", "High", "Medium", "Low" },
{ "Intensity", "High", "Medium", "Low" },
};
static int i = 0;
DefaultMutableTreeNode root, child, chosen;
JTree tree;
DefaultTreeModel model;
public Trees() {
setLayout(new BorderLayout());
root = new DefaultMutableTreeNode("root");
tree = new JTree(root);
// Add it and make it take care of scrolling:
add(new JScrollPane(tree),
BorderLayout.CENTER);
// Capture the tree's model:
model =(DefaultTreeModel)tree.getModel();
JButton test = new JButton("Press me");
test.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if(i < data.length) {
child = new Branch(data[i++]).node();
// What's the last one you clicked?
chosen = (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
if(chosen == null) chosen = root;
// The model will create the
// appropriate event. In response, the
// tree will update itself:
model.insertNodeInto(child, chosen, 0);
// This puts the new node on the
// currently chosen node.
}
}
});
// Change the button's colors:
test.setBackground(Color.blue);
test.setForeground(Color.white);
JPanel p = new JPanel();
p.add(test);
add(p, BorderLayout.SOUTH);
}
public static void main(String args[]) {
Show.inFrame(new Trees(),200,500);
}
} ///:~
The
first class,
Branch,
is a tool to take an array of
String
and build a DefaultMutableTreeNode
with the first
String
as the root and the rest of the
Strings
in the array as leaves. Then
node( )
can be called to produce the root of this “branch.”
The
Trees
class contains a two-dimensional array of
Strings
from which
Branches
can be made and a
static
int i
to count through this array. The
DefaultMutableTreeNode
objects hold the nodes, but the physical representation on screen is controlled
by the
JTree
and its associated model, the DefaultTreeModel.
Note that when the
JTree
is added to the applet, it is wrapped in a JScrollPane
– this is all it takes to provide automatic scrolling.
The
JTree
is controlled through its
model.
When you make a change to the model, the model generates an event that causes
the JTree
to perform any necessary updates to the visible representation of the tree. In
init( ),
the model is captured by calling getModel( ).
When the button is pressed, a new “branch” is created. Then the
currently selected component is found (or the root if nothing is selected) and
the model’s insertNodeInto( )
method does all the work of changing the tree and causing it to be updated.
Most
of the time an example like the one above will give you what you need in a
tree. However, trees have the power to do just about anything you can imagine
– everywhere you see the word “default” in the example above,
you can substitute your own class to get different behavior. But beware: almost
all of these classes have a large interface, so you could spend a lot of time
struggling to understand the intricacies of trees.
Tables
Like
trees, tables
in Swing are vast and powerful. They are primarily intended to be the popular
“grid” interface to databases via Java Database Connectivity (JDBC,
discussed in Chapter 15) and thus they have a tremendous amount of flexibility,
which you pay for in complexity. There’s easily enough here to be the
basis of a full-blown spreadsheet and could probably justify an entire book.
However, it is also possible to create a relatively simple
JTable
if you understand the basics.
The
JTable
controls how the data is displayed, but the
TableModel
controls the data itself. So to create a
JTable
you’ll typically create a
TableModel
first. You can fully implement the
TableModel
interface, but it’s usually simpler to inherit from the helper class
AbstractTableModel:
//: Table.java
// Simple demonstration of JTable
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
// The TableModel controls all the data:
class DataModel extends AbstractTableModel {
Object[][] data = {
{"one", "two", "three", "four"},
{"five", "six", "seven", "eight"},
{"nine", "ten", "eleven", "twelve"},
};
// Prints data when table changes:
class TML implements TableModelListener {
public void tableChanged(TableModelEvent e) {
for(int i = 0; i < data.length; i++) {
for(int j = 0; j < data[0].length; j++)
System.out.print(data[i][j] + " ");
System.out.println();
}
}
}
DataModel() {
addTableModelListener(new TML());
}
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
public void
setValueAt(Object val, int row, int col) {
data[row][col] = val;
// Indicate the change has happened:
fireTableDataChanged();
}
public boolean
isCellEditable(int row, int col) {
return true;
}
};
public class Table extends JPanel {
public Table() {
setLayout(new BorderLayout());
JTable table = new JTable(new DataModel());
JScrollPane scrollpane =
JTable.createScrollPaneForTable(table);
add(scrollpane, BorderLayout.CENTER);
}
public static void main(String args[]) {
Show.inFrame(new Table(),200,200);
}
} ///:~
DataModel
contains an array of data, but you could also get the data from some other
source such as a database. The constructor adds a
TableModelListener
which prints the array every time the table is changed. The rest of the methods
follow the Beans naming convention, and are used by
JTable
when it wants to present the information in
DataModel.
AbstractTableModel
provides default methods for
setValueAt( )
and
isCellEditable( )
that prevent changes to the data, so if you want to be able to edit the data,
you must override these methods.
Once
you have a
TableModel,
you only need to hand it to the
JTable
constructor. All the details of displaying, editing and updating will be taken
care of for you. Notice that this example also puts the
JTable
in a
JScrollPane,
which requires a special
JTable
method.
Tabbed
Panes
Earlier
in this chapter you were introduced to the positively medieval CardLayout,
and saw how you had to manage all the switching of the ugly cards yourself.
Someone actually thought this was a good design. Fortunately, Swing remedies
this by providing
JTabbedPane,
which handles all the tabs, the switching, and everything. The contrast between
CardLayout
and
JTabbedPane
is breathtaking.
The
following example is quite fun because it takes advantage of the design of the
previous examples. They are all built as descendants of
JPanel,
so this example will place each one of the previous examples in its own pane on
a
JTabbedPane.
You’ll notice that the use of RTTI makes the example quite small and
elegant:
//: Tabbed.java
// Using tabbed panes
package c13.swing;
import java.awt.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
public class Tabbed extends JPanel {
static Object[][] q = {
{ "Felix", Borders.class },
{ "The Professor", Buttons.class },
{ "Rock Bottom", ButtonGroups.class },
{ "Theodore", Faces.class },
{ "Simon", Menus.class },
{ "Alvin", Popup.class },
{ "Tom", ListCombo.class },
{ "Jerry", Progress.class },
{ "Bugs", Trees.class },
{ "Daffy", Table.class },
};
static JPanel makePanel(Class c) {
String title = c.getName();
title = title.substring(
title.lastIndexOf('.') + 1);
JPanel sp = null;
try {
sp = (JPanel)c.newInstance();
} catch(Exception e) {
System.out.println(e);
}
sp.setBorder(new TitledBorder(title));
return sp;
}
public Tabbed() {
setLayout(new BorderLayout());
JTabbedPane tabbed = new JTabbedPane();
for(int i = 0; i < q.length; i++)
tabbed.addTab((String)q[i][0],
makePanel((Class)q[i][1]));
add(tabbed, BorderLayout.CENTER);
tabbed.setSelectedIndex(q.length/2);
}
public static void main(String args[]) {
Show.inFrame(new Tabbed(),460,350);
}
} ///:~
Again,
you can see the theme of an array used for configuration: the first element is
the
String
to be placed on the tab and the second is the
JPanel
class that will be displayed inside of the corresponding pane. In the
Tabbed( )
constructor, you can see the two important
JTabbedPane
methods that are used: addTab( )
to put a new pane in, and setSelectedIndex( )
to choose the pane to start with. (One in the middle is chosen just to show
that you don’t have to start with the first pane.)
When
you call
addTab( )
you supply it with the
String
for the tab and any Component
(that is, an AWT
Component,
not just a
JComponent,
which is derived from the AWT
Component).
The
Component
will be displayed in the pane. Once you do this, no further management is
necessary – the
JTabbedPane
takes care of everything else for you (as it should).
The
makePanel( )
method takes the
Class
object of the class you want to create and uses newInstance( )
to create one, casting it to a
JPanel
(of course, this assumes that any class you want to add must inherit from
JPanel,
but that’s been the structure used for the examples in this section). It
adds a TitledBorder
that contains the name of the class and returns the result as a
JPanel
to be used in
addTab( ).
When
you run the program you’ll see that the
JTabbedPane
automatically stacks the tabs if there are too many of them to fit on one row.
The
Swing message box
Windowing
environments commonly contain a standard set of message
boxes that allow you to quickly post information to the user or to capture
information from the user. In Swing, these message boxes are contained in JOptionPane.
You have many different possibilities (some quite sophisticated), but the ones
you’ll most commonly use are probably the message dialog and confirmation
dialog, invoked using the
static
JOptionPane.showMessageDialog( )
and
JOptionPane.
showConfirmDialog( )
.
More
to Swing
This
section was meant only to give you an introduction to the power of Swing and to
get you started so you could see how relatively simple it is to feel your way
through the libraries. What you’ve seen so far will probably suffice for
a good portion of your UI design needs. However, there’s a lot more to
Swing – it’s intended to be a fully-powered UI design tool kit. If
you don’t see what you need here, delve into the online documentation
from Sun and search the Web. There’s probably a way to accomplish just
about everything you can imagine.
Some
of the topics that were not covered in this section include:
- More
specific components such as
JColorChooser,
JFileChooser,
JPasswordField,
JHTMLPane
(which performs simple HTML formatting and display), and JTextPane
(a text editor that supports formatting, word wrap, and images). These are
fairly straightforward to use.
- The
new event types for Swing. In many ways, these are like exceptions: the type is
what’s important, and the name can be used to infer just about everything
else about them.
- New
layout managers:
Springs
& Struts (a la Smalltalk) and BoxLayout.
Splitter
control: a divider style splitter bar that allows you to dynamically manipulate
the position of other components.
JLayeredPane
and JInternalFrame,
used together to create child frame windows inside parent frame windows, to
produce multiple-document
interface
(MDI)
applications.
- Pluggable
look and feel, so you can write a single program that can dynamically adapt to
behave as expected under different platforms and operating systems.
- Custom
cursors.
- Dockable
floating
toolbars
with the JToolbar
API.
- Double-buffering
and Automatic repaint batching for smoother screen redraws.
- Built-in
[60]
At the time this section was written, the Swing library had been pronounced
“frozen” by Sun, so this code should compile and run without
problems as long as you’ve downloaded and installed the Swing library.
(You should be able to compile one of Sun’s included demonstration
programs to test your installation.) If you do encounter difficulties, check
www.BruceEckel.com
for updated code.
[61]
This may also be a result of using pre-beta software.