Introduction to Swing [60]

Bruce Eckel’s Thinking in Java Contents | Prev | Next

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.

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.

Benefits
of Swing

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.

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

jc.setToolTipText("My
tip");

Borders

//: 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);
  }
} ///:~ 

Buttons

//: 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);
  }
} ///:~ 

Button
groups

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.

Icons

//: 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);
  }
} ///:~ 

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.

Popup
menus

//: 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);
  }
} ///:~ 

List
boxes and combo boxes

//: 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

//: 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);
  }
} ///:~ 

Trees

add(new JTree(
  new Object[] {"this", "that", "other"}));

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);
  }
} ///:~ 

Tables

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

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

The
Swing message box

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
  • 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:
Splitter
control: a divider style splitter bar that allows you to dynamically manipulate
the position of other components.

  • 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
  • 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.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read