Basic Swing Components

The goal of this chapter is to expose you to different Swing components, their uses and personalities. Each component has a program that illustrates its use. All programs are structured in the same way, using the SwingApp class defined in the previous chapter. For each program, you can independently study its layout (i.e. containment hierarchy) and its actions (event listeners). In fact, each program is partitioned into two segments to delineate this separation. By studying them, you will observe a tremendous amount of structure that they have. It is this structure that GUI builders, such as VisualAge and J++, exploit and thereby make GUI programming easier. Also, you will see that this structure simplifies program understanding.

The examples of this chapter are in this self-extracting zip file.

JEditorPanes and JPasswordFields

Besides JTextField and JTextArea, Swing offers other textField components. JEditorPane can display not only text, but html files as well. In fact, there is a setPage(url) method associated with JEditorPanes that can load a file or html page given its URL! Another text field is JPasswordField.  It is a subclass of JTextField and performs identically, except that textual input is hidden.

Note: a JEditorPane object do not understand html pages with embedded JavaScript or ActiveX components.  If you attempt to view such pages with a JEditorPane, errors will be reported.

URLexample illustrates both kinds of components. Note that when there are errors in reading or creating URLs, error messages are displayed in the JEditorPane. The critical statements dealing with JPasswordField are shown in blue and those for JEditorPane are shown in yellow.

// URLexample illustrates use of JEditor pane
// and the ability to load html pages

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.URL;

public class URLexample extends SwingApp {

   // Atomic Components

   JTextField     visibleInput;
   JPasswordField hiddenInput;
   JEditorPane    output;
   JLabel         visibleInputLabel;
   JLabel         hiddenInputLabel;
   JLabel         outputLabel;

   // init atoms

   public void initAtoms() {

      // initialize superclass atoms first
      super.initAtoms();

      // init labels
      visibleInputLabel = new JLabel("URL:");
      hiddenInputLabel  = new JLabel("hidden URL:");
      outputLabel       = new JLabel("URL Contents");

      // output is an initially empty JEditorPane that
      // cannot be edited.  

      output = new JEditorPane();
      output.setEditable(false);

      // visibleInput text field is 30 chars long
      // when CR is pressed, the contents of this field
      // are assumed to be a URL; so the whenPressed action
      // converts this string into a URL object and the
      // output page is loaded with its contents

      visibleInput = new JTextField(30);
      visibleInput.setToolTipText("type URL address here");

      // same thing for hiddenInput

      hiddenInput = new JPasswordField(30);
      hiddenInput.setToolTipText("type URL address here");
   }

   // Layout Components

   JScrollPane outputScroll;
   JPanel      visibleInputRow;
   JPanel      hiddenInputRow;
   JPanel      outputRow;
 
   // initialize Layout Components

   public void initLayout() {

      // perform superclass initializations first

      super.initLayout();

      // scroll the JEditor object. Set the preferred size (because
      // not doing so will yield a microscopically small pane)
      // and always show a vertical scrollbar

      outputScroll = new JScrollPane(output);
      outputScroll.setPreferredSize(new Dimension(250,150));
      outputScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

      // visibleInputRow will be a horizontal BoxLayout JPanel
      // containing the input label and input field

      visibleInputRow = new JPanel();
      visibleInputRow.setLayout( 
         new BoxLayout(visibleInputRow, BoxLayout.X_AXIS) );
      visibleInputRow.setBorder( 
         BorderFactory.createEmptyBorder(10,//top
                                         10,//left
                                         10,//bottom
                                         10 // right
                                         ) );
      visibleInputRow.add(visibleInputLabel);
      visibleInputRow.add(visibleInput);

      // hiddenInputRow is similar to visibleInputRow

      hiddenInputRow = new JPanel();
      hiddenInputRow.setLayout(
         new BoxLayout(hiddenInputRow, BoxLayout.X_AXIS) );
      hiddenInputRow.setBorder(
         BorderFactory.createEmptyBorder(10,//top
                                         10,//left
                                         10,//bottom
                                         10 // right
                                         ) );
      hiddenInputRow.add(hiddenInputLabel);
      hiddenInputRow.add(hiddenInput);
 
      // outputRow is a vertical BoxLayout JPanel
      // containing output label and outputScroll
 
      outputRow = new JPanel();
      outputRow.setLayout( new BoxLayout(outputRow, BoxLayout.Y_AXIS) );
      outputRow.setBorder( BorderFactory.createEmptyBorder(10,//top
                                                      10,//left
                                                      10,//bottom
                                                      10 // right
                                                      ) );
      outputRow.add(outputLabel);
      outputRow.add(outputScroll);
   }

   public void initContentPane() {
      
      // ContentPane uses a BoxLayout of a single column
      // where visibleInputRow and outputRow are stored.  this code 
      // overrides any previous contentPane setting

      ContentPane = new JPanel();
      ContentPane.setLayout( new BoxLayout(ContentPane, BoxLayout.Y_AXIS) );
      ContentPane.setBorder( BorderFactory.createEmptyBorder(30,//top
                                                             30,//left
                                                             10,//bottom
                                                             30 // right
                                                             ) );
      ContentPane.add(visibleInputRow);
      ContentPane.add(hiddenInputRow);
      ContentPane.add(outputRow);
   }

  public void commonAction(JTextField in) {
      URL myURL;
      try { myURL = new URL(in.getText()); } 
      catch (Exception e) {
         error(in, "Couldn't create URL ", e);
         return;
      }
      try { output.setPage(myURL); } 
      catch (Exception e) {
         error(in, "Couldn't load URL ", e);
      }
   }

   void error(JTextField in, String msg, Exception e) {
      output.setText(msg + in.getText() + "\n" + e.getMessage());
   }

   public void initListeners() {

      // action taken when visibleInput is submitted

      visibleInput.addActionListener( new ActionListener() {
         public void actionPerformed( ActionEvent e ) {
            commonAction(visibleInput);
         }
      });

      // action taken when hiddenInput is submitted
  
      hiddenInput.addActionListener( new ActionListener() {
         public void actionPerformed( ActionEvent e ) {
            commonAction(hiddenInput);
         }
      });
   }

   public URLexample() { super(); }

   public URLexample(String AppTitle ) { super(AppTitle); }

   public static void main(String[] args) { 
      new URLexample("URLexample"); 
   }
}

Dialogs

We can improve URLexample by noting that we really didn't have a good place to put error messages. What we should have used are dialogs. Dialogs are pop-up windows that alert users to some condition or that request some input before proceeding. There are all sorts of dialog windows that can be displayed, including error messages, warnings, information notes, and questions. The table below lists some (but not all) dialog boxes that can be used:

String s = JOptionPane.showInputDialog( "input url here" );
Object[] options = { "OPTION0", "OPTION1", "OPTION2" };
int r = JOptionPane.showOptionDialog(null, 
             "Select one of the following options", "Question", 
        JOptionPane.DEFAULT_OPTION, 
        JOptionPane.QUESTION_MESSAGE,
        null, options, options[0]);
switch( r ) {
  0: /* OPTION0 selected */
  1: /* OPTION1 selected */
  2: /* OPTION2 selected */
  default: /* window close selected */
}
JOptionPane.showMessageDialog( null, "this is my message" );
 JOptionPane.showMessageDialog(null, "my error msg",
             "Error!", JOptionPane.ERROR_MESSAGE);

They are all simple to express:

JOptionPane.showMessageDialog( parentFrame, messageString, dialogStringTitle, dialogType )

parentFrame links the dialog window as a child to the Swing application that created it. Thus, if the Swing application window is killed, so too will its dialog window. The message to be displayed is messageString, and the title of the dialog box is dialogStringTitle. The parameter dialogType can be instantiated with a number of constants to indicate which graphic to display. For example, ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, or PLAIN_MESSAGE are possibilities. (Note that these are static constants of JOptionPane).

Program URLexample2 refines URLexample1: the error method is overridden to provide dialog support (as the figure below shows):

// URLexample2 illustrates use of dialog windows for
// error reporting.

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class URLexample2 extends URLexample {

   // override error method

   void error(JTextField in, String msg, Exception e) {
      JOptionPane.showMessageDialog(null, msg + in.getText() +
              "\n" + e.getMessage(), "Error!", JOptionPane.ERROR_MESSAGE);
   }

   public URLexample2() { super(); }

   public URLexample2(String AppTitle ) { super(AppTitle); }

   public static void main(String[] args) { 
      new URLexample2("Example URLexample2"); 
   }
}

Check Boxes

checkBoxDemo illustrates two features: the use of check boxes and labels with GIF images. A check box is a way to collect yes/no information on a small list of items. Every check box has a label. When the yes/no value of the box is toggled, an ItemEvent is generated.

JLabels can display GIF images, and these images can change at application run-time. In this program, when a checkbox is selected, an image is loaded into a predetermined label. Run checkBoxDemo.

The source of checkBoxDemo is below. The critical statements on check boxes is shown in yellow and those with labels are in blue:

// checkBoxDemo illustrates use of check boxes
// and labels with GIF images

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class checkBoxDemo extends SwingApp {
   // define all the gif files

   public static final String borisGif    = "GIFS/Boris.gif";
   public static final String natashaGif  = "GIFS/Natasha.gif";
   public static final String mooseGif    = "GIFS/Moose.gif";
   public static final String rockyGif    = "GIFS/Rocky.gif";
   public static final String emptyGif    = "GIFS/Green.jpg";

   // Atomic Component declarations

   JLabel      borisLabel;
   JLabel      natashaLabel;
   JLabel      mooseLabel;
   JLabel      rockyLabel;

   JCheckBox   boris;
   JCheckBox   natasha;
   JCheckBox   moose;
   JCheckBox   rocky;

   public void initAtoms() {
      // do any initialization in superclass 
      super.initAtoms();

      // init labels
      borisLabel   = new JLabel( new ImageIcon(emptyGif) );
      natashaLabel = new JLabel( new ImageIcon(emptyGif) );
      mooseLabel   = new JLabel( new ImageIcon(emptyGif) );
      rockyLabel   = new JLabel( new ImageIcon(emptyGif) );

      // now initialize check boxes - assume none are selected
      boris = new JCheckBox("Boris");
      boris.setSelected(false);

      natasha = new JCheckBox("Natasha");
      natasha.setSelected(false);

      moose = new JCheckBox("Bullwinkle");
      moose.setSelected(false);

      rocky = new JCheckBox("Rocky");
      rocky.setSelected(false);
   }

   // layout component declarations
   // all check boxes are placed vertically in a GridLayout JPanel

   JPanel  checkableOptions;
  
   public void initLayout() {
      checkableOptions = new JPanel();
      checkableOptions.setLayout( new GridLayout(0,1) );
      checkableOptions.setBorder(BorderFactory.createEtchedBorder());
      checkableOptions.add(boris);
      checkableOptions.add(natasha);
      checkableOptions.add(moose);
      checkableOptions.add(rocky);
   }

   // the ContentPane horizonally displays the checkableOptions and labels

   public void initContentPane() {
      ContentPane = new JPanel();
      ContentPane.setLayout( new GridLayout(1,0));
      ContentPane.setBorder(BorderFactory.createEtchedBorder());
      ContentPane.add(checkableOptions);
      ContentPane.add(borisLabel);
      ContentPane.add(natashaLabel);
      ContentPane.add(mooseLabel);
      ContentPane.add(rockyLabel);
   }

   // a local class declaration; an object of CheckBoxListener listens
   // for all ItemEvents and deciphers the event and displays the appropriate
   // gif image.

   class CheckBoxListener implements ItemListener {
      public void itemStateChanged(ItemEvent e) {

         JCheckBox selectedItem = (JCheckBox) e.getItemSelectable();

         if (selectedItem == boris) 
             fix(borisLabel, boris.isSelected(), borisGif);
         else if (selectedItem == natasha) 
             fix(natashaLabel, natasha.isSelected(), natashaGif);
         else if (selectedItem == moose)   
             fix(mooseLabel, moose.isSelected(), mooseGif);
         else if (selectedItem == rocky)   
             fix(rockyLabel, rocky.isSelected(), rockyGif);
         else { 
            JOptionPane.showMessageDialog(checkBoxDemo.this, "unrecognized ItemEvent",
                "Error!", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
         }
      }

      void fix(JLabel b, boolean active, String gif) {
         b.setIcon( new ImageIcon(active?gif:emptyGif) );
      }
   }

   CheckBoxListener cbl;

   public void initListeners() {
      cbl = new CheckBoxListener();
      boris.addItemListener(cbl);
      natasha.addItemListener(cbl);
      moose.addItemListener(cbl);
      rocky.addItemListener(cbl);
   }

   public checkBoxDemo() { super(); };

   public checkBoxDemo(String AppTitle) { super(AppTitle); } 

   public static void main(String[] args) {
     new checkBoxDemo("checkBoxDemo");
   }
}

Radio Buttons

Radio buttons are used when one of a small set of mutually exclusive options is to be selected. Each radio button has a label. When it is selected, an ActionItem event is triggered. In the radioButtonDemo program, a radio listener changes the icon picture to the button that was selected. Run radioButtonDemo and try the idea experiments listed in the program source, which is shown below.

Notice two things in the program source. First, a ButtonGroup object is created to contain all radio button objects, but the ButtonGroup object itself is not displayed. Its purpose is to coordinate the selection of only one radio button at a time. Second, associated with each radio button is a string called an "action command". When a button is selected, the string of this button can be read and some action performed on it. In the radioButtonDemo, the "action command" for a button is the name of a GIF file. The action performed on this string when its button is selected is to display that GIF file.  Critical code for radio buttons is highlighted in blue.

// radioButtonDemo illustrates use of Radio Buttons

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class radioButtonDemo extends SwingApp {
   // define GIF files

   public static final String borisGif    = "Gifs/Boris.gif";
   public static final String natashaGif  = "Gifs/Natasha.gif";
   public static final String mooseGif    = "Gifs/Moose.gif";
   public static final String rockyGif    = "Gifs/Rocky.gif";
   public static final String emptyGif    = "Gifs/Green.jpg";

   // Atomic Component declarations

   JRadioButton  boris;
   JRadioButton  natasha;
   JRadioButton  moose;
   JRadioButton  rocky;
   JLabel        picture;

   public void initAtoms() {
      // do any initialization in superclass 
      super.initAtoms();

      // initialize Radio Buttons - always set one to be true
      // note use of action commands (very useful in general)

      boris = new JRadioButton("Boris");
      boris.setSelected(true);
      boris.setActionCommand(borisGif);

      natasha = new JRadioButton("Natasha");
      natasha.setSelected(false);
      natasha.setActionCommand(natashaGif);

      moose = new JRadioButton("Bullwinkle");
      moose.setSelected(false);
      moose.setActionCommand(mooseGif);

      rocky = new JRadioButton("Rocky");
      rocky.setSelected(false);
      rocky.setActionCommand(rockyGif);

      // peculiar to Radio buttons is that they must
      // be placed in a group - nothing is done with the
      // group object after it is created though...

      ButtonGroup group = new ButtonGroup();
      group.add(boris);
      group.add(natasha);
      group.add(moose);
      group.add(rocky);

      // make selected picture display boris which we assumed
      // to be selected above

      picture = new JLabel( new ImageIcon(borisGif) );
   }

   // layout component declarations
   // RadioPanel vertically stacks each radio button and 
   // surrounds them with an etched border.  RadioPanel2
   // surrounds RadioPanel with an empty-space border.
   // Idea: remove RadioPanel2 to see what happens...

   JPanel  RadioPanel;
   JPanel  RadioPanel2;
  
   public void initLayout() {
      RadioPanel = new JPanel();
      RadioPanel.setLayout( new GridLayout(0,1) );
      RadioPanel.setBorder(BorderFactory.createEtchedBorder());
      RadioPanel.add(boris);
      RadioPanel.add(natasha);
      RadioPanel.add(moose);
      RadioPanel.add(rocky);

      RadioPanel2 = new JPanel();
      RadioPanel2.setLayout( new GridLayout(0,1) );
      RadioPanel2.setBorder(BorderFactory.createEmptyBorder(30,20,30,20));
      RadioPanel2.add(RadioPanel);
   }

   // Note: the Empty Border sizes were picked specifically for this
   // example.  Idea: replace with (5,5,5,5) to see what happens.
   // in general, it is best to compute the size of images dynamically.
   // Look at methods of ImageIcon...

   public void initContentPane() {
     // ContentPane uses a GridLayout of a single column
     // where input, accum, and submitButton are stored

     ContentPane = new JPanel();
     ContentPane.setLayout( new GridLayout(1,0) );
     ContentPane.setBorder(BorderFactory.createEtchedBorder());
     ContentPane.add(RadioPanel2);
     ContentPane.add(picture);
   }

   // a local class declaration - here's where
   // the action takes place.  When a radio button is
   // clicked, an instance of RadioListener responds
   // by determining which button was selected,
   // reading its "action command", which is a string
   // of the gif file name, and reloads the picture
   // pane with the gif file.

   class RadioListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
         String name = e.getActionCommand();
         picture.setIcon( new ImageIcon( name ) );
      }
   }

   RadioListener rl;
   
   public void initListeners() {
      rl = new RadioListener();
      boris.addActionListener(rl);
      natasha.addActionListener(rl);
      moose.addActionListener(rl);
      rocky.addActionListener(rl);
   }

   public radioButtonDemo() { super(); } 

   public radioButtonDemo(String AppTitle) { super(AppTitle); } 

   public static void main(String[] args) {
     new radioButtonDemo("radioButtonDemo");
   }
}

Combo Boxes

A JComboBox is yet another way to display a small list of items. It appears as a pull-down menu, where the selectable items are on the menu itself. The comboBoxDemo application shows a simple use of combo boxes. As items are selected, their GIF files are depicted in a horizontal array and the item is removed from the list.   Thus, as selections are made, the ComboBox list grows shorter as the display array fills up. The reset button reinitializes the array and list. Run comboBoxDemo and try the idea experiments listed in its source.

A key idea in writing JComboBox programs is the relationship between a ComboBox and a ComboBoxModel.  A ComboBoxModel holds the list elements that are to be displayed.  All changes to this list are made to the ComboBoxModel (or in our case, an instance of DefaultComboBoxModel class).  A JComboBox, in contrast, merely displays the contents of its model and generates events that let you know that JComboBox selections have taken place.  When such an event is received, you need to examine the event object to determine its source (i.e., to determine which item of the box was selected). 

A key interface is ActionListener.  An ActionEvent is fired whenever an option is selected from a JComboBox or when the DefaultComboBoxModel is updated. (Actually, when an event is fired by updating the DefaultComboBoxModel is not entirely evident. It appears that an event is fired when all elements are deleted, or when the first element is added. No events are fired on subsequent additions or deletions. Sigh...). The fact that the same event is fired for two different reasons can cause an infinite recursion of the ActionListener event handler -- when a selection is made, the event handler is invoked. Inside the event handler, the DefaultComboBoxModel is updated, which reinvokes the handler. Upon re-invocation, the handler sees that a selection was made and proceeds to update the DefaultComboBoxModel which reinvokes the handler, and so on. The boolean doNothing, in the program's source, is used to break this infinite recursion.

The source for comboBoxDemo is shown below. The critical statements for initializing a ComboBox is in yellow, and those statements for editing a ComboBoxModel are in blue.

// comboBoxDemo illustrates use of comboBoxDemoes
// note use of doNothing - updating comboModel list
// triggers events in comboBoxDemo (and screws things up).

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class comboBoxDemo extends SwingApp {
   public static final String emptyGif    = "Gifs/Green.jpg";
   public static Object[] initChoices = 
             { "Boris", "Natasha", "Moose", "Rocky" };

   // Atomic Component declarations

   JLabel     Label[];
   JComboBox  combo;
   JButton    reset;

   // supporting objects

   DefaultComboBoxModel comboModel;
   int                  index;            // which picture to install next
   boolean              doNothing;

   public void initAtoms() {
      // do any initialization in superclass 
      super.initAtoms();

      // initialize supporting objects
      index = 0;
      doNothing = false;
      comboModel = new DefaultComboBoxModel(initChoices);

      // init gif labels
      index = 0;
      Label = new JLabel[4];
      Label[0]  = new JLabel( new ImageIcon(emptyGif) );
      Label[1]  = new JLabel( new ImageIcon(emptyGif) );
      Label[2]  = new JLabel( new ImageIcon(emptyGif) );
      Label[3]  = new JLabel( new ImageIcon(emptyGif) );

      // now initialize ComboBox
      combo = new JComboBox(comboModel);

      // init button
      reset = new JButton("Reset");
      reset.setToolTipText("Reset ComboBox");
   }

   // layout component declarations

   JPanel  selectableOptions;
  
   public void initLayout() {
      selectableOptions = new JPanel();
      selectableOptions.setLayout( new GridLayout(0,1) );
      selectableOptions.setBorder(BorderFactory.createEtchedBorder());
      selectableOptions.add(reset);
      selectableOptions.add(combo);
   }

   public void initContentPane() {
      ContentPane = new JPanel();
      ContentPane.setLayout( new GridLayout(1,0));
      ContentPane.setBorder(BorderFactory.createEtchedBorder());
      ContentPane.add(selectableOptions);
      ContentPane.add(Label[0]);
      ContentPane.add(Label[1]);
      ContentPane.add(Label[2]);
      ContentPane.add(Label[3]);
   }

   public void initListeners() {

      // action taken when comboBox is selected

      combo.addActionListener( new ActionListener(){
         public void actionPerformed(ActionEvent e) {
             if (!doNothing) {
                JComboBox cb = (JComboBox) e.getSource();
                String    name = (String) cb.getSelectedItem();
                Label[index++].setIcon( new ImageIcon(
                           "Gifs/" + name + ".gif") );
                comboModel.removeElement( name );
             }
         }
      } );

      // action taken when reset is clicked

      reset.addActionListener( new ActionListener() {
         public void actionPerformed( ActionEvent e ) {
            index = 0;
            Label[0].setIcon( new ImageIcon(emptyGif) );
            Label[1].setIcon( new ImageIcon(emptyGif) );
            Label[2].setIcon( new ImageIcon(emptyGif) );
            Label[3].setIcon( new ImageIcon(emptyGif) );

            doNothing = true;
            comboModel.removeAllElements();
            comboModel.addElement("Boris");
            comboModel.addElement("Natasha");
            comboModel.addElement("Moose");
            comboModel.addElement("Rocky");
            doNothing = false;
         }
      });
   }

   public comboBoxDemo() { super(); }

   public comboBoxDemo(String AppTitle) { super(AppTitle); } 

   public static void main(String[] args) {
     new comboBoxDemo("comboBoxDemo");
   }
}

JLists

The JList component allows users to select from a large list of options and the list of options themselves is editable at run-time. The listDemo illustrates the editing of two JLists, allowing selected items from one list to be moved to the second and vice versa. 

The source for listDemo is shown below. The actual list of elements to display is managed by a DefaultListModel object. A JList object displays the contents of a DefaultListModel object. When edits are to be made to a list, the edits are performed on the DefaultListModel object, and not to the JList object.

Similar to the DefaultComboBoxModel, whenever an element is selected from a list or whenever a DefaultListModel is updated, a ListSelectionListener event is fired. The fact that updating a DefaultListModel triggers the same event as a list selection is problemmatic because without careful consideration, the ListSelectionListener event handler will recurse infinitely -- a element selection invokes the ListSelectionListener event handler, but inside this handler a DefaultListModel is updated which in turn reinvokes the handler, which in turn sees than an element selection was invoked, and so on. A boolean nowUpdating is introduced into the event handler to prevent such recursion.

Highlighted in blue are the critical statements of a DefaultListModel; yellow statements are critical to JLists. 

// listDemo illustrates use of dynamically changeable JLists

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class listDemo extends SwingApp {

   // Atomic Component declarations

   JList            rightHandList;
   JList            leftHandList;
   DefaultListModel rightModel;
   DefaultListModel leftModel;

   public void initAtoms() {
      super.initAtoms();

      // initialize the models (contents) of the lists
      // one edits models and lets the JList display their
      // contents

      rightModel = new DefaultListModel();
      rightModel.addElement("Boris");
      rightModel.addElement("Natasha");
      rightModel.addElement("Moose");
      rightModel.addElement("Squirrel");

      leftModel = new DefaultListModel();

      // now initialize the lists using their models
      // only make the first 3 rows visible (so that
      // we need a scroll pane.  Also, only allow
      // one element to be selected at any one time.

      rightHandList = new JList(rightModel);
      rightHandList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      rightHandList.setVisibleRowCount(4);

      leftHandList = new JList(leftModel);
      leftHandList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      leftHandList.setVisibleRowCount(4);
   }

   // layout component declarations

   JScrollPane   rightScrollPane;
   JScrollPane   leftScrollPane;

   public void initLayout() {

      // layout consists of two scrolled lists

      rightScrollPane = new JScrollPane(rightHandList);
      leftScrollPane = new JScrollPane(leftHandList);
   }

   public void initContentPane() {
     // ContentPane uses a GridLayout of a single row
     // where list and picture are stored

     ContentPane = new JPanel();
     ContentPane.setLayout( new GridLayout(1,0) );
     ContentPane.setBorder(BorderFactory.createEtchedBorder());
     ContentPane.add(leftScrollPane);
     ContentPane.add(rightScrollPane);
   }

   static boolean nowUpdating = false;

   class listListener implements ListSelectionListener {

      DefaultListModel companionModel;

      listListener( JList companion ) { 
         companionModel = (DefaultListModel) companion.getModel(); 
      }
       
      public void valueChanged(ListSelectionEvent e) {
      
         DefaultListModel listModel;

         // act only when the value is not rapidly changing
   
         if (e.getValueIsAdjusting())
            return;

         // do nothing if nothing was selected or we're
         // in the middle of updating lists.
         // however if something was selected, remove it and 
         // add it to the companion

         JList theList = (JList)e.getSource();

         if (theList.isSelectionEmpty() || nowUpdating)
            return;
         else {
            nowUpdating = true;
            int index = theList.getSelectedIndex();
            listModel = (DefaultListModel) theList.getModel();
            companionModel.addElement(listModel.getElementAt(index));
            listModel.removeElementAt(index);
            nowUpdating = false;
         }
      }
   }

   listListener rightListener;
   listListener leftListener;
   
   public void initListeners() {

      // the companion to the rightListener is leftHandList;
      // the companion to the leftListener is rightHandlist;

      rightListener = new listListener(leftHandList);
      leftListener  = new listListener(rightHandList);

      rightHandList.addListSelectionListener(rightListener);
      leftHandList.addListSelectionListener(leftListener);
   }

   public listDemo() { super(); } 

   public listDemo(String AppTitle) { super(AppTitle); } 

   public static void main(String[] args) {
     new listDemo("listDemo");
   }
}

JTables

The JTable component displays the contents of a table.  It is very versatile and has seemingly endless ways of being customized.  However, with this generality comes a price -- it can be difficult to program.  Fortunately, the simple things such as displaying a table, detecting updates to the table, adding rows, etc. is fairly easy to do.  The TableDemo program illustrates a number of different JTable features and whose GUI is shown below:

These features are:

The key concept behind the JTable component is that you must create a TableModel (or in our case, an instance of the DefaultTableModel) as the object that maintains the contents of the table.  A DefaultTableModel can be instantiated by supplying a 2-dimensional array of objects (i.e., of type Object[][]) as the table contents and a 1-dimensional array of objects (typically of type String[]) to indicate the names of column headers. A typical code fragment to instantiate a model is:

Object[][] data = { { "(0,0)", "(0,1)", "(0,2)" }  // row 0
                    { "(1,0)", "(1,1)", "(1,2)" }  // row 1
                  };
String[] columnNames = { "column0", "column1" };
DefaultTableModel tm = new DefaultTableModel( data, columnNames );

An alternative is to use vectors (i.e., the data variable would be a vector of vectors and the columnNames variable would be a vector) to instantiate a DefaultTableModel.

The JTable component displays the contents of a TableModel, much like a JList displays a ListModel and a JComboBox displays a ComboBoxModelJTable has more than its share of quirks.  Unless a JTable is presented within a JScrollPane, the column names of the table are not displayed.  Further, each JTable is assigned by default a fixed amount of screen real estate, so if you want anything else, you may need to adjust its size manually. Thus, typical code sequence to instantiate a JTable is:

JTable      t = new JTable( tm );
t.setPreferredScrollableViewportSize( new Dimension( 450, 80 ) ); // default size is (450,400)
JScrollPane p = new JScrollPane( t );

There is nothing particularly fancy about the TableDemo application.  Where you will find it useful is gaining a better understanding of how a JTable works.  It is possible for a GUI client to update the contents of a table element.  (Try it by running TableDemo). However, there can be a delay in which this update is actually registered with the underlying TableModel.  That is, you can modify the table directly in the GUI, but only after a TableChanged event has been triggered will your TableModel actually "see" the modification.  During this delay -- which is quite noticable -- the change will be visible to you, but not the program itself.  Run TableDemo to see this -- modify the (3,1) entry of a displayed table, and click the show-row button to see if the change has been registered in the TableModel.

To detect if a row has been selected, use the following code fragment:

t.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );   // select one row at a time
...
// code to react when row is selected

ListSelectionModel rowSM = t.getSelectionModel();
rowSM.addListSelectionListener(new ListSelectionListener() {
   public void valueChanged(ListSelectionEvent e) {
      if (e.getValueIsAdjusting())
         return;
      ListSelectionModel l = (ListSelectionModel)e.getSource();
      if (l.isSelectionEmpty()) {
         //no rows are selected
      } else {
         selectedRow = l.getMinSelectionIndex();
         // selectedRow has been selected
        }
}});

To detect if a table has been modified, listen for a TableModelEvent:

tm.addTableModelListener( new TableModelListener() {
    public void tableChanged( TableModelEvent e) {
        // table has been modified
     }
 });

Finally, if you have several tables to display, but only one needs to be displayed at a time, you can change the table model of a JTable. This changes your GUI's containment hierarchy, so it is recommended that you also revalidate the ContentPane when you make this change.

tm = new_table_model;
t.setModel(tm);		// replace old table model with new
ContentPane.validate();    // revalidate containment hierarchy

Changing TableModel objects is useful when you create a GUI that is a front-end to a database. The results of a retrieval can be expressed as a TableModel; the results of different retrievals would be expressed as different TableModel objects.  Thus, when a new retrieval is performed, a new TableModel is created to hold the results.  You use the above code fragment to throw away the old model and replace it with the new model.

The source for TableDemo is shown below.  Code that is shown in yellow deals with tables; code that is shown in blue deals with table events and updates:

// TableDemo -- illustrates how tables work

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import javax.swing.event.*;
import javax.swing.table.*;
import java.util.*;

public class TableDemo extends SwingApp {

   // REMEMBER -- all constants should be static
   // the following define the initial contents of both table models

   static String columnNames[] = { "Name", "Male", "Number", "Country" };

   static Object data[][] = {
          { "Boris",    "true", "1", "Russia"   },
          { "Natasha",  "false","2", "Ukraine" },
          { "Moose",    "true", "3", "Canada"   },
          { "Squirrel", "true", "4", "United States" }, };

   static String columnNames2[] = { "First", "Last" };

   static Object data2[][] = {  { "George", "Bush" },
                                { "Hillary", "Clinton" },
                                { "Prem", "Devanbu" } };

   static Object emptyRow[] = { "", "", "", "" };

   // declare and initialize atomic components here

   int 	             selectedRow ;    // last row selected by user
   JButton           show31, delRow0, addRow, swapTables;
   JTextArea         actions;
   JTable            t;
   DefaultTableModel tm, tmshadow;

   public void initAtoms() {
      selectedRow = 0;
      show31     = new JButton( "show row 3, column 1" );
      delRow0    = new JButton("delete row 0");
      addRow     = new JButton("add empty row");
      swapTables = new JButton("swap tables");
      actions    = new JTextArea(6,20);
      tmshadow   = new DefaultTableModel( data2, columnNames2 );
      tm         = new DefaultTableModel( data, columnNames );
      t          = new JTable( tm );

      // This is the screwy part about Tables -- 
      // by default their size is Dimension( 450, 400 ).
      // If you want anything else, you must set the size manually

      t.setPreferredScrollableViewportSize( new Dimension( 450, 80 ) );

      // only if table is to be updated -- we are interested
      // in notifications to the selection of individual rows
 
      t.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
   }

   // declare and initialize layout components here

   JPanel      buttons;
   JScrollPane js;

   public void initLayout() {
      buttons = new JPanel();
      buttons.setLayout( new FlowLayout() );
      buttons.setBorder(BorderFactory.createEtchedBorder());
      buttons.add(show31);
      buttons.add(delRow0);
      buttons.add(addRow);
      buttons.add(swapTables);

      js = new JScrollPane( t );
   }

   // initialize ContentPane here

   public void initContentPane() {
      ContentPane = new JPanel();
      ContentPane.setLayout( new BoxLayout(ContentPane, BoxLayout.Y_AXIS) );
      ContentPane.setBorder(BorderFactory.createEtchedBorder());
      ContentPane.add(buttons);
      ContentPane.add(js);
      ContentPane.add(new JScrollPane(actions));
   }

   // initialize listeners here

   public void initListeners() {

     // react when either table model is modified

     tm.addTableModelListener( new TableModelListener() {
        public void tableChanged( TableModelEvent e) {
           actions.append(selectedRow + " has been modified\n");
     }
     });

     tmshadow.addTableModelListener( new TableModelListener() {
        public void tableChanged( TableModelEvent e) {
           actions.append(selectedRow + " has been modified\n");
     }
     });

     // react when row is selected

     ListSelectionModel rowSM = t.getSelectionModel();
     rowSM.addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {
            if (e.getValueIsAdjusting())
               return;
            ListSelectionModel lsm = (ListSelectionModel)e.getSource();
            if (lsm.isSelectionEmpty()) {
               //no rows are selected
            } else {
            selectedRow = lsm.getMinSelectionIndex();
            actions.append( "row " + selectedRow + " has been selected\n");
        }
     }});

     // when show31 button is pushed...

     show31.addActionListener( new ActionListener() {
        public void actionPerformed( ActionEvent e ) {
           String result = null;
           if (tm.getRowCount() < 4 ) 
              result = "row 3 doesn't exist";
           else
              result = "table[3,1] = " + ((String) tm.getValueAt(3,1));
           actions.append(result + "\n" );
        }
     } );

     // when delete row 0 button is pushed...

     delRow0.addActionListener( new ActionListener() {
        public void actionPerformed( ActionEvent e ) {
            tm.removeRow(0);
            selectedRow = 0;
            actions.append( "row " + selectedRow + " has been selected\n");
        }
     });

     // when add row button is pushed...

     addRow.addActionListener( new ActionListener() { 
        public void actionPerformed( ActionEvent e ) {
            tm.addRow( emptyRow );
        }
     });

     // when swapTables button is pushed...

     swapTables.addActionListener( new ActionListener() { 
        public void actionPerformed( ActionEvent e ) {
            DefaultTableModel tmp = tm;
            tm = tmshadow;
            tmshadow = tmp;
            t.setModel(tm);
            ContentPane.validate();    // important!
        }
     });
   }

   // place in this method any action for exiting application

   public void applicationExit() { }

   public TableDemo() { super(); } 

   public TableDemo(String AppTitle) { super(AppTitle); } 

   public static void main(String[] args) {
      new TableDemo("TableDemo");
   }
}

JMenuBar, JMenu, and JMenuItems

A menu bar and menus are easy to create. A menu bar is represented by a JMenuBar object. Individual menus (such as "File" and "Edit" in the figure below) are JMenu objects that are added to the JMenuBar. Each item of a menu is either a terminal option (represented by a JMenuItem object) or a JMenu object (which represents a nested or "walking" menu). The MenuBar object is added to the JFrame object by calling the setJMenuBar method.

Selecting any JMenuItem fires an ActionEvent. So registering and reacting to the selection of a JMenuItem is no different than registering and reacting to a push of a JButton.

The menuDemo program illustrates the creation and use of menu bars, menus, and their event handlers. The "File" menu has MenuItems "Open", "Close", and "Exit". The "Edit" menu has a nested menu "Options", which has MenuItems "Copy" and "Cut". When menuDemo runs, each JMenuItem selected will be noted in a JTextField. Statements critical to the construction of the menu bar are indicated in blue in the source below; statements critical to processing the selection of JMenuItems are shown in yellow.

// menuDemo.java 

import SwingUtils.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class menuDemo extends SwingApp implements ActionListener {

   // atomic components 

   JMenuBar    menuBar;
   JMenu       fileMenu;
   JMenu       editMenu;
   JMenu       optionsMenu;
   JMenuItem   openItem;
   JMenuItem   closeItem;
   JMenuItem   exitItem;
   JMenuItem   copyItem;
   JMenuItem   cutItem;
   JTextArea   usage;
   JScrollPane scrollUsage;

   public void initAtoms() {
      menuBar = new JMenuBar();
      setJMenuBar(menuBar);
     
      // instantiate all objects of the file menu

      fileMenu  = new JMenu( "File" );
      openItem  = new JMenuItem( "Open" );
      closeItem = new JMenuItem( "Close" );
      exitItem  = new JMenuItem( "Exit" );
      openItem  = new JMenuItem( "Open" );

      // assemble file menu hierarchy

      fileMenu.add( openItem );
      fileMenu.add( closeItem );
      fileMenu.addSeparator();
      fileMenu.add( exitItem ); 

      // instantiate all objects of the edit menu

      editMenu    = new JMenu( "Edit" );
      optionsMenu = new JMenu( "Options" );
      copyItem    = new JMenuItem( "Copy" );
      cutItem     = new JMenuItem( "Cut" );
     
      // assemble edit menu hierarchy

      editMenu.add( optionsMenu );
      optionsMenu.add( copyItem );
      optionsMenu.add( cutItem  );

      // assemble objects of menu bar

      menuBar.add( fileMenu );
      menuBar.add( editMenu );

      // create text area

      usage = new JTextArea( 6, 20 );
      scrollUsage = new JScrollPane( usage );
   }

   public void initContentPane() {
      ContentPane = new JPanel();
      ContentPane.setLayout( new GridLayout(1,0) );
      ContentPane.setBorder(BorderFactory.createEtchedBorder());
      ContentPane.add( scrollUsage );
   }

   public void initListeners() {
      openItem.addActionListener( this );
      closeItem.addActionListener( this );
      exitItem.addActionListener( this );
      cutItem.addActionListener( this );
      copyItem.addActionListener( this );
   }

   public void actionPerformed( ActionEvent e ) {
      if (e.getSource() == openItem)
         usage.append("open selected\n");
      else
      if (e.getSource() == closeItem)
         usage.append("open selected\n");
      else
      if (e.getSource() == exitItem) 
         System.exit(0);
      else
      if (e.getSource() == cutItem)
         usage.append("cut selected\n");
      else
      if (e.getSource() == copyItem)
         usage.append("copy selected\n");
      else
         usage.append("unknown selected\n");
   }

   public menuDemo() { super(); } 

   public menuDemo(String AppTitle) { super(AppTitle); } 

   public static void main(String[] args) {
      new menuDemo("menuDemo");
   }
}

File Chooser

The JFileChooser component allows users to navigate through a file system and select files, directories, or both. JFileChooser is highly customizable and supports file filtering and other features. For details, consult the Swing web page for JFileChooser. The main window of fileChooserDemo is simple: it consists of two buttons and a log (JTextArea) of user selections. When either button is pushed, a file chooser dialog window appears, which allows for navigation and selection. Run fileChooserDemo. Note that a JFileChooser has no GUI, per se. It does use dialogs to prompt for input.

The source of fileChooserDemo is shown below. The important statements on JFileChooser are indicated in blue.

// fileChooserDemo illustrates use of FileChooser

import SwingUtils.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.filechooser.*;

public class fileChooserDemo extends SwingApp {
   final static String newline = "\n";

   // Atomic Component Declarations

   JTextArea     log;
   JFileChooser  chooser;
   JButton       openButton;
   JButton       saveButton;

   public void initAtoms() {
      ImageIcon icon;

      super.initAtoms();

      // Create log to be a 5x20 text area

      log = new JTextArea(5,20);
      log.setEditable(false);

      // Create a file chooser, where it begins in directory C:\
      // allow both files and directories to be selected.  Default
      // is FILES_ONLY
      chooser = new JFileChooser("C:\\");
      chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

      // Create buttons
      icon = new ImageIcon("images/open.gif");
      openButton = new JButton("Open File...", icon);

      icon = new ImageIcon("images/save.gif");
      saveButton = new JButton("Save File...", icon);
    }

    // Layout Component Declarations

    JPanel      buttonPanel;
    JScrollPane logScrollPane;

    public void initLayout() {

       // place open and save buttons on single panel that
       // is default a FlowLayout

       buttonPanel = new JPanel();
       buttonPanel.add(openButton);
       buttonPanel.add(saveButton);

       logScrollPane = new JScrollPane(log);
    }

    // the ContentPane shows the buttonPanel on top and a scrollable
    // log pane on the bottom.

    public void initContentPane() {
        ContentPane = new JPanel();
        ContentPane.setLayout( new BorderLayout() );
        ContentPane.setBorder( BorderFactory.createEmptyBorder(10,//top
                                                               10,//left
                                                               10,//bottom
                                                               10 // right
                                                               ) );
        ContentPane.add(buttonPanel, BorderLayout.NORTH);
        ContentPane.add(logScrollPane, BorderLayout.CENTER);
    }

public void initListeners() {
      // action for open button clicks

      openButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
             int returnVal = chooser.showOpenDialog(fileChooserDemo.this);

             if (returnVal == JFileChooser.APPROVE_OPTION) {
                File file = chooser.getSelectedFile();
                // this is where a real application would open the file.
                log.append("Opening: " + file.getName() + "." + newline);
             } else {
                log.append("Open command cancelled by user." + newline);
             }
         }
      });

      // action for save button clicks

      saveButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            int returnVal = chooser.showSaveDialog(fileChooserDemo.this);

            if (returnVal == JFileChooser.APPROVE_OPTION) {
               File file = chooser.getSelectedFile();
               // this is where a real application would save the file.
               log.append("Saving: " + file.getName() + "." + newline);
            } else {
               log.append("Save command cancelled by user." + newline);
            }
         }
      });
    }

   public fileChooserDemo() { super(); }

   public fileChooserDemo(String AppTitle) { super(AppTitle); }

   public static void main(String[] args) {
      new fileChooserDemo("fileChooserDemo");
   }
}

Tabbed Panels

JTabbedPanels allows different panels to share the same space, where only one panel is displayed at a time. The TabbedPanels application takes several of the previous Swing programs and unifies them into a single program with tabbed panels, one panel per program. Run TabbedPanels and note that state information per panel is retained.

The source of TabbedPanels is below. The important statements are indicated in blue.

// TabbedPanels connects previously written Swing
// applications into single application that
// uses tabbed panels

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.net.URL;
import SwingUtils.*;

public class TabbedPanels extends SwingApp {

   // no Atomic Components

   // Layout Components

   checkBoxDemo    checkBox;       // Tab 1
   radioButtonDemo radioButton;    // Tab 2
   comboBoxDemo    comboBox;       // Tab 3
   fileChooserDemo fileChooser;    // Tab 4

   JTabbedPane tabbedPane;         // JTabbedPane
 
   // initialize Layout Components
   public void initLayout() {

      // perform superclass initializations first
      super.initLayout();

      // initialize each tab

      checkBox    = new checkBoxDemo();
      radioButton = new radioButtonDemo();
      comboBox    = new comboBoxDemo();
      fileChooser = new fileChooserDemo();

      // now create the tabbed pane

      tabbedPane = new JTabbedPane();
      ImageIcon  nullIcon = null;

      tabbedPane.addTab("checkBoxDemo",    nullIcon, checkBox.ContentPane   );
      tabbedPane.addTab("radioButtonDemo", nullIcon, radioButton.ContentPane);
      tabbedPane.addTab("comboBoxDemo",    nullIcon, comboBox.ContentPane   );
      tabbedPane.addTab("fileChooserDemo", nullIcon, fileChooser.ContentPane);
   }

   public void initContentPane() {
      
      // ContentPane contains a tabbed pane

      ContentPane = new JPanel();
      ContentPane.setLayout( new GridLayout(1,1) );
      ContentPane.add(tabbedPane);
   }

// no listeners

   public TabbedPanels(String AppTitle) { 
      super(AppTitle);
   }

   public static void main(String[] args) { 
      new TabbedPanels("TabbedPanels");
   }
}

Recap

There are many other Swing components, each with innumerable features:

I recommend that you create your applications with the components that we reviewed and investigate other components that were not covered. Swing components have personalities that are only exposed through usage. The examples given above will get you started.  Consult the Campione text and the JDK1.3 documentation for further details on Swing components.