by Michael Shoffner

Making the Forum Swing, Part 1

how-to
Sep 1, 199824 mins

Find out how to develop and deploy a commercial-quality GUI with Swing

In previous versions of Java, the Abstract Windowing Toolkit (AWT) provided the basis for GUI development using Java.

The AWT has taken a lot of heat for its shortcomings, which included lack of decent widgets, a weird 1.0 event model, inconsistent behavior across platforms, and large functionality deficiencies.

These problems have been addressed in Java’s 1.2 release through the inclusion of the Java Foundation Classes (JFC). The JFC contains much additional feature support, including a new set of GUI widgets and support classes called Swing.

Note: To develop with Swing and Java 1.2 and deploy your apps on the Internet/intranet, you’ll need to download and install JDK 1.2 and Java Plug-in. See the sidebar “Tips for installing JDK 1.2 Beta 4 and the Java Plug-in” for details.

Intro to JFC

The JFC provides the new core Java reference platform for Java GUI and client-side development. JFC contains the following components:

  • Swing API: New classes and interfaces for building GUIs

  • Accessibility API: Interface support features for the disabled

  • 2D API: True 2D graphics support

  • Pluggable Look and Feel: Ability to define a particular look and feel and change application look and feel on the fly

  • Printing and Drag and Drop APIs: Printing and inter-application desktop drag-and-drop capabilities

The Swing portion of JFC is really all we’re interested in for rebuilding the Forum.

Spring into Swing

Swing is the crown jewel of the JFC. It is, in essence, the “new AWT,” and as such it supercedes the old AWT (although support for the old AWT is supported in Java 1.2 for backwards compatibility).

This portion of the Swing hierarchy is analogous to the AWT hierarchy

The architecture of Swing components is loosely based on the Model-View-Controller (MVC) design pattern. MVC separates an entity that needs to present data into three parts — the model, the view, and the controller. This responsibility split allows a component’s rendering to be decoupled from its data and event handling.

Swing classes and interfaces come in two varieties: user interface (UI) classes, such as widgets, and non-UI classes, such as event classes and utility classes for widgets.

The com.sun.java.swing package and the Swing components

The com.sun.java.swing package is the main Swing package. It contains all the Swing widgets and components and most of their supporting classes.

These are the additional new components that Swing has to offer

Swing’s components form a new class hierarchy parallel to the original AWT. The root of this hierarchy is JComponent. Swing’s UI classes are based on the lightweight component facility introduced in Java 1.1. Lightweight components are pure Java, and therefore have no native peers.

All Swing components have the following characteristics, among others:

  • They are beans
  • They are lightweight
  • Their borders may be changed
  • They support flicker elimination (so you don’t have to)

The purpose of most of the Swing components can be inferred by their names. Some of the more interesting widget types include:

  • Data-aware table displays (JTable)
  • Data-aware tree displays (JTree)
  • Scrollable panes (JScrollPane)
  • Pop-up menus (JPopupMenu)
  • Tooltips (JToolTip)
  • Toolbars (JToolBar)
  • Labels and buttons that support images (JLabel and JButton)
  • Frames within frames (JInternalFrame)

We will use some of these components when we rebuild the Forum client.

Swing’s approach to look and feel

The new Swing widgets are pretty exciting, as you’ll see soon, but the icing on the cake is Swing’s pluggable look and feel (L&F) capability.

The Swing Forum, displayed with the Motif L&F

A pluggable look and feel allows an application to be rendered in multiple platform-specific styles. Swing provides several L&Fs, including Motif, Windows, Metal, and Mac. The application’s look and feel is set with a call to the UIManager class. The look and feel can even be changed while the application is running, although it’s not terribly convenient to do so.

The new Forum client

As you may recall, the old version of the Forum worked in the 1.0.2 environment and used the 1.0.2 event model and AWT widgets. It worked adequately, but its appearance left much to be desired, and didn’t compare favorably with Microsoft Foundation Classes (MFC) or Mac GUIs.

The Forum is definitely ready for a face lift. This month we’re going to create the GUI and hook up some of Forum’s event handling. Next month we’ll finish up the event handling and write the communications layer that will talk to the Forum Server. Click on the image below to take the new and improved Swing Forum out for a spin.

No JDK 1.1 support for APPLET!

The Swing Forum consists of two classes: SwingForumLauncher, an applet or standalone app that launches the SwingForum frame; and SwingForum class, which contains the guts of the GUI and event handling.

Writing the SwingForumLauncher class

The SwingForumLauncher is a hybrid applet and standalone application that simply sets up the SwingForum frame and shows it.

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import java.util.*;
import java.net.*;
import java.io.*;

public class SwingForumLauncher extends JApplet {

  Container contents;
  JLabel starter;

  public void init () {
    contents = getContentPane ();
    final URL cb = getCodeBase ();
    String bgcolor = getParameter ("bgcolor");
    if (bgcolor != null) {
      try {
        contents.setBackground (new Color (Integer.parseInt(
                    bgcolor.substring (1), 16))); 
      } catch (NumberFormatException ex) {
        System.out.println ("Invalid format for bgcolor:" + bgcolor);
      }
    }
    contents.setLayout (new FlowLayout (FlowLayout.CENTER));
    Image image = getImage (cb, getParameter ("icon"));
    starter = new JLabel (new ImageIcon (image));
    starter.setToolTipText ("Click to Launch SwingForum");
    contents.add (starter);
    final Hashtable icons = new Hashtable ();
    starter.addMouseListener (new MouseAdapter () {
      public void mouseClicked (MouseEvent e) {
        // load images
        showStatus ("Loading Swing Forum...");
        icons.put ("Title", new ImageIcon (getImage (cb, 
          getParameter ("title"))));
        icons.put ("Read", new ImageIcon (getImage (cb, 
          getParameter ("read"))));
        icons.put ("Compose", new ImageIcon (getImage (cb, 
          getParameter ("compose"))));
        icons.put ("Reply", new ImageIcon (getImage (cb, 
          getParameter ("reply"))));
        icons.put ("ID", new ImageIcon (getImage (cb, getParameter ("id"))));
        showSwingForum (icons);
        showStatus ("Swing Forum loaded.");
      }
    });
  }

This portion of the code is applet-specific. The applet displays a clickable image within the HTML page in which it is embedded. If the mouse is left over the image, a tooltip will pop up to explain what clicking on the image does.

  SwingForum client;

  void showSwingForum (Hashtable icons) {
    if (client == null) {
      client = new SwingForum (icons);
    }
    client.setVisible (true);
  }

  public static void main (String args []) {
    String imagePath = System.getProperty ("user.dir") + 
               File.separator + "images" + File.separator;
    Hashtable icons = new Hashtable ();
    icons.put ("Title", new ImageIcon (imagePath + "bn.gif"));
    icons.put ("Read", new ImageIcon (imagePath + "beermug.gif"));
    icons.put ("Compose", new ImageIcon (imagePath + "bomfl.gif"));
    icons.put ("Reply", new ImageIcon (imagePath + "blhorn.gif"));
    icons.put ("ID", new ImageIcon (imagePath + "comhap.gif"));
    new SwingForumLauncher ().showSwingForum (icons);
  }
}

The showSwingForum(...) method is shared by both the applet and the application, and passes a Hashtable of Icons to the SwingForum constructor.

The static main() method creates an instance of the launcher class, loads a Hashtable with ImageIcons, and passes the Hashtable to the showSwingForum(...) method.

The interesting thing here is the use of ImageIcon. The ImageIcon constructor accepts a filename or URL as an argument and returns an image icon that can be displayed in other Swing components such as labels and buttons. This ability alone is worth the price of admission, because dealing with images under the old AWT was quite painful.

Writing the SwingForum class

The SwingForum class is the real meat of the Swing Forum client. It contains all the widgets and handles all the events from those widgets.

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.sun.java.swing.*;
import com.sun.java.swing.tree.*;

public class SwingForum extends JFrame {

  Hashtable icons = new Hashtable ();

  public SwingForum (Hashtable i) {
    icons = i;
    setTitle ("The Swing Forum");
    setDefaultCloseOperation (WindowConstants.HIDE_ON_CLOSE);
    // setLAF (new com.sun.java..swing.plaf.windows.WindowsLookAndFeel ());
    setLAF (new com.sun.java.swing.plaf.motif.MotifLookAndFeel ());
    setUpActions ();
    layOutMenuBar ();
    layOutTree ();
    layOutDisplay ();
    layOutContentPane ();
    pack ();
    setVisible (true);
  }

The SwingForum constructor exemplifies the procedure for constructing a Swing app. We’ll simply follow this sequence of custom method calls to see the details of how the app works. Note that all the methods prior to layOutContentPanel(...) must be called before layOutContentPanel(...) is called; these methods instantiate the components that it uses to do the overall layout for the frame.

Not surprisingly, SwingForum extends JFrame. This approach is very similar to extending the old AWT Frame component. There are a few key differences, though. One is the existence of the setDefaultCloseOperation(...) call, which relieves us of the necessity to implement the WindowListener interface to catch window closing events.

  public Dimension getPreferredSize () {
    return new Dimension (625, 465);
  }

  void setLAF (LookAndFeel laf) {
    try {
      UIManager.setLookAndFeel (laf);
    } catch (UnsupportedLookAndFeelException ex) {
      ex.printStackTrace ();
    }
  }

The first custom method call sets the look and feel. Notice that the setLAF(...) method can easily be called with another L&F. If there is no call to setLAF(...), the default L&F will be used by the UIManager.

We also set the size of the Swing Forum frame here by overriding the getPreferredSize() method to make it return the size we want. This method can easily be changed to make the frame smaller or bigger.

  Action readAction, composeAction, replyAction, 
         refreshAction, identityAction, closeAction, helpAction;

  void setUpActions () {
    readAction = new AbstractAction ("Read") {
      public void actionPerformed (ActionEvent e) {
        display.setSelectedComponent (readTab);
      }
    };
    replyAction = new AbstractAction ("Reply") {
      public void actionPerformed (ActionEvent e) {
        display.setSelectedComponent (replyTab);
      }
    };
    composeAction = new AbstractAction ("Compose") {
      public void actionPerformed (ActionEvent e) {
        display.setSelectedComponent (composeTab);
      }
    };
    refreshAction = new AbstractAction ("Refresh") {
      public void actionPerformed (ActionEvent e) {
        System.out.println ("Do a REFRESH");
      }
    };
    identityAction = new AbstractAction ("Identity") {
      public void actionPerformed (ActionEvent e) {
        display.setSelectedComponent (idTab);
      }
    };
    closeAction = new AbstractAction ("Close") {
      public void actionPerformed (ActionEvent e) {
        setVisible (false);
      }
    };
    helpAction = new AbstractAction ("Help") {
      public void actionPerformed (ActionEvent e) {
        System.out.println ("Show HELP");
      }
    };
  }

Now we’re starting to get into the useful high-level facilities that Swing provides. AbstractAction is the encapsulation of a user action. A single instance of one of its subclasses, such as the "Read" action defined above using the Java 1.1 anonymous class facility, may be passed as an argument to several different controls.

This capability is great because it simplifies event handling for multiple controls. Instead of implementing listener interfaces for the same type of event from a toolbar and a menu, for example, you need only define and create one instance of an AbstractAction, and pass it to both.

Note that the above statements both define subclasses of AbstractAction that correspond to each Swing Forum action and instantiate these subclasses. The subclasses provide an implementation of the actionPerformed (...) method that handles the performance of the action by the user. Whenever the identityAction occurs, for example, the display tabbed panel (which we’ll examine shortly) is flipped to the ID panel, which lets the user enter or change his/her identification.

  JMenuBar menubar;

  void layOutMenuBar () {
    menubar = new JMenuBar ();
    JMenu m = new JMenu ("File");
    m.add (closeAction);
    m.add (refreshAction);
    menubar.add (m);
    m = new JMenu ("Mode");
    m.add (readAction);
    m.add (replyAction);
    m.add (composeAction);
    m.add (identityAction);
    menubar.add (m);
    m = new JMenu ("Help");
    m.add (helpAction);
    // menubar.setHelpMenu (m); - not yet implemented in JDK!
    menubar.add (m);
  }

Here we lay out the JMenuBar for the application. Notice that we’re adding the actions we defined earlier to the JMenus instead of adding JMenuItems. JMenu knows to add a JMenuItem for every subclass of AbstractAction we add to it.

  JTree tree;
  JScrollPane treeScroller;
  DefaultTreeModel treeModel;
  DefaultMutableTreeNode root;

  void layOutTree () {
    root = new DefaultMutableTreeNode ("Forum Server");
    treeModel = new DefaultTreeModel (root);
    tree = new JTree (treeModel);
    treeScroller = new JScrollPane (tree);

    // populate tree 
    // this will come from server in next version
    treeModel.insertNodeInto (new DefaultMutableTreeNode (
                 "Thread 1"), root, 0);
    treeModel.insertNodeInto (new DefaultMutableTreeNode (
                 "Thread 2"), root, 1);
    treeModel.insertNodeInto (new DefaultMutableTreeNode (
                 "Thread 3"), root, 2);
    treeModel.insertNodeInto (new DefaultMutableTreeNode (
                 "Thread 4"), root, 3);
  }

Here we set up the JTree that will end up on the west side of the interface, contained within a JScrollPane so that it can scroll if it gets larger than the space allotted to it by the layout manager.

The JTree component is a full-featured tree component. Its nodes are of type DefaultMutableTreeNode, and they are added to its data model, which is of type DefaultTreeModel.

The tree’s data model is used to manipulate the nodes in the tree. Any change to the data model is automatically reflected in the tree view. The nodes we’ve added above are merely placeholders. When we finish up the application in the next installment, the nodes will contain the threads and articles retrieved from the server.

  JTabbedPane display;
  JPanel readTab, replyTab, composeTab, idTab;
  JTextArea readArea, replyArea, composeArea;
  JTextField idField;

  void layOutDisplay () {
    display = new JTabbedPane ();

    // read
    readTab = new JPanel ();
    readTab.setLayout (new BorderLayout ());
    readTab.add ("North", new JLabel ("Read current message"));
    readTab.add ("Center", readArea = new JTextArea ());
    readArea.setEditable (false);
    Icon i = (Icon) icons.get ("Read");
    display.addTab ("Read", i, readTab, "Read the currently selected message");

    // reply
    replyTab = new JPanel ();
    replyTab.setLayout (new BorderLayout ());
    replyTab.add ("North", new JLabel ("Reply to current message"));
    replyTab.add ("Center", replyArea = new JTextArea ());
    i = (Icon) icons.get ("Reply");
    display.addTab ("Reply", i, replyTab, "Reply to the currently 
                 selected message (quoted with >'s)");

Here we see the setup for the JTabbedPane, which serves as the article-reading and composing real estate of the GUI. The display functions much like the old CardLayout; you can add components to it and flip to them based on events.

    // compose
    composeTab = new JPanel ();
    composeTab.setLayout (new BorderLayout ());
    composeTab.add ("North", new JLabel ("Compose new message"));
    composeTab.add ("Center", composeArea = new JTextArea ());
    i = (Icon) icons.get ("Compose");
    display.addTab ("Compose", i, composeTab, "Compose a new message");

    // identity
    idTab = new JPanel ();
    idTab.setLayout (new BorderLayout ());
    idTab.add ("North", new JLabel ("Set identity"));
    Panel q = new Panel ();
    q.add (new JLabel ("Please enter your name:"));
    q.add (idField = new JTextField (15));
    idTab.add ("Center", q);
    i = (Icon) icons.get ("ID");
    display.addTab ("Identity", i, idTab, "Enter your identity below. 
           You must do this to post messages");
  }

Notice that with each addTab(...) call we’re using an image icon from the Hashtable that was constructed and passed in by the launcher class. We’re also adding a tooltip that will pop up whenever the mouse lingers over the respective tab.

  Container contents;  

  void layOutContentPane () {
    setJMenuBar (menubar);
    contents = getContentPane ();
    Icon i = (Icon) icons.get ("Title");
    contents.add ("North", new JLabel (getTitle (), i, JLabel.LEFT));
    contents.add ("Center", display);
    contents.add ("West", treeScroller);
  }
}

We’ve taken care of all the components, so what’s left? layOutContentPane (), the grand finale in setting up the application. We do the overall layout with a BorderLayout, which gives the tree and title label their preferred sizes and adds the display tabbed panel in the remaining space.

Herein we also see the main difference between using a Swing JFrame and a standard AWT Frame. With a standard AWT Frame, you set a layout and add components directly to the Frame itself. Not so with JFrame. This component has a “content pane,” to which you add all widgets. You obtain a reference to the content pane with a call to the getContentPane() method. Once you have a reference to the content pane, you proceed to set a layout manager on it and add components in the normal way.

That’s it. We’ve completed the majority of the client side of the Swing Forum. Now it’s time to deploy it.

Deploying the Swing Forum client over the Web/intranet

As we’ve seen, Swing is pretty neat, but everything isn’t perfect. The main problem with Swing and the JFC, and increasingly with client-side Java in general, is lack of support from Web browsers. This makes Web/intranet deployment of Java applets somewhat difficult.

Sun’s solution to this problem is the Java Plug-in (formerly known as project Java Activator). The Java Plug-in allows developers to use Sun’s latest JVM and class libraries as the runtime environment for their Java applets by adapting their HTML code to accommodate it. Sun provides an HTML converter for this purpose.

Creating the HTML file for Java Plug-in The following HTML tag is designed to be used by both Netscape and Internet Explorer for the purposes of invoking the Java Plug-in to run the Swing Forum.

<HTML><HEAD><TITLE>The Swing Forum</TITLE>
<BODY>

<CENTER>
   <OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
         width="100" height="100" align="baseline"
     codebase="http://java.sun.com/products/plugin/1.1/jinstall-11-win32.cab#Version=1,1,0,0">
     <PARAM NAME="code" VALUE="SwingForumLauncher.class">
     <PARAM NAME="codebase" VALUE="step/">
     <PARAM NAME="bgcolor" VALUE="#ffffff">
     <PARAM NAME="type" VALUE="application/x-java-applet;version=1.1">
     <PARAM NAME="icon" VALUE="images/bigbn.gif">
     <PARAM NAME="title" VALUE="images/bn.gif">
     <PARAM NAME="read" VALUE="images/beermug.gif">
     <PARAM NAME="compose" VALUE="images/bomfl.gif">
     <PARAM NAME="reply" VALUE="images/blhorn.gif">
     <PARAM NAME="id" VALUE="images/comhap.gif">
     <COMMENT>
         <EMBED type="application/x-java-applet;version=1.1" width="100"
         height="100" align="baseline" code="SwingForumLauncher.class" 
         codebase="step/" 
         bgcolor="#ffffff"
         icon="images/bigbn.gif"
     title="images/bn.gif"
     read="images/beermug.gif"
         compose="images/bomfl.gif"
         reply="images/blhorn.gif"
         id="images/comhap.gif"
         pluginspage="http://java.sun.com/products/plugin/1.1/plugin-install.html">
         <NOEMBED>
     </COMMENT>
         No JDK 1.1 support for APPLET!!
         </NOEMBED>
         </EMBED>
   </OBJECT>
</CENTER>

</BODY>
</HTML>

The idea here is that IE will read only the <OBJECT> tag and NS will read only the <EMBED> tag. The above setup allows one HTML file to cover both browsers.

Patterning your own HTML file after the above will allow you to embed your own Swing/Java 1.2-based applets in client Web browsers.

Deploying the Swing Forum as a standalone application

You can also deploy Swing Forum as a standalone application. For this setup, you’ll need to distribute the Java Runtime Environment (JRE) to client machines along with your compiled code. You’ll also need some sort of installer, unless the user will install by hand.

The JRE consists of the Java virtual machine and the Java core class libraries. A JRE installer is avaliable from Sun’s Java Software Division for distribution with your class files.

Conclusion

Swing is a boon to client-side Java development because of its power. With Swing, you no longer have to deal with flicker problems, cross-platform inconsistencies, the absence of standard widgets, or a host of other uglies. In addition, you also get pluggable L&F support and the ability to take advantage of other JFC features.

The only drawback to developing with the Java 1.2 technologies is the lack of browser support — and that can be alleviated by using the Java Plug-in.

The more you get, the better it is. So, in the next installment, we’ll finish up the Swing Forum. First, we’ll hook up the events; then we’ll activate the tree by connecting it to a Forum server.

Little is known about the secret writings of Nicola Tesla on the subject of time displacement through pentagahedronal antennas actuated by rapid frequency-shifting oscillators. Michael’s primary research is directed at recovering some of these technologies. If successful, Michael plans to go back in time and get one of those “Dewey Beats Truman” newspapers. And a 1943 copper penny.