Java: A platform for platforms
Sun's reorg may seem promising to shareholders but it's also a scramble for position. The question now is whether Sun can, or wants to, maintain its hold on Java technology. Especially with enterprise leaders like SpringSource and RedHat investing heavily in Java's future as a platform for platforms

Also see:

Discuss: Tim Bray on 'What Sun Should Do'

Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Making the Forum Swing, Part 2

Find out how to event-enable and network a Swing-based client

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Swing, the powerful GUI toolkit bundled with JDK 1.2, includes a lot of useful widgets, support for multiple look and feels, and a variety of other features you'll want to exploit in your apps. In our last installment we used Swing to give our tired old Forum message client a more commercial-quality interface. This time we're going to outfit the Swing Forum with the ability to read articles and threads from the Forum server, as well as post articles to it.

We'll start with the interface code we developed last time. To this we'll add listeners to enable reading, replying, and posting functionality. We're going to reuse a networking class from a previous installment to implement the networking to the Forum server, which we'll also reuse from a previous installment.

In response to a reader suggestion, we're going to add a JSplitPane between the tree on the left and the tabbed display area on the right.

On JTree and TreeModels

Before diving into the new code, we should take a closer look at the interface component that will be the focus of most of our effort: the JTree.

The JTree is the central widget in the Swing Forum. Users interact with the tree to select threads and the articles they contain. The tree reflects the server content (or a portion of it) at the point in time that the tree refreshes its contents from the server.

The tree appearing in the interface represents an interaction of several types of objects:

  • JTree handles the view of the tree data

  • DefaultTreeModel handles the data the view displays

  • DefaultMutableTreeNode encapsulates a node in the tree model

  • ForumArticle encapsulates article contents


The JTree displays data from the DefaultTreeModel, which is composed of DefaultMutableTreeNodes.

The JTree itself handles the tasks of displaying the tree structure and handling user events. The contents of the tree are stored in a tree data model that implements the TreeModel interface; in this case, we use the supplied DefaultTreeModel. The tree model is made up of tree nodes, each of which is an instance of DefaultMutableTreeNode.

Each DefaultMutableTreeNode instance contains a "user object" that represents user data at that node in the tree. In our case, the user object is either a String (the name of a thread) or an instance of ForumArticle (which encapsulates an article).

To allow the user to manipulate the threads and articles on the server, we need to define user actions on the tree for refreshing and navigating.

We'll specify that double mouse clicks make refresh calls for threads and the root node (the Forum server node), and we'll enable navigation of the tree with single mouse clicks and arrow keys.

Communications and networking

Because the server is the reference copy of articles and threads, we need to find some way to access the server and display its contents in the tree. Our old friend networking comes to the rescue here.

We'll enable networking with a sockets-based communications object that implements the 1.0 Forum API. The 1.0 Forum API has three methods, which are called by the Swing Forum event handlers based on user interaction with the GUI. The signatures for these methods are defined in the SwingForumClientCom interface.

  • Hashtable loadAllThreads ()
  • Vector loadThreadArticles (String thread)
  • boolean postArticle (String art, String thread)


Of course, any communications object that implements this interface will work in place of the sockets-based object we're using; we designed the system way back when for just this sort of flexibility.

For the purposes of the Swing Forum, we've modified an old ForumClientComSocketImpl to take a URL in its constructor. We won't detail the code for this or the server portions of the article -- they're basically taken whole-cloth from past installments (see Resources for a listing of past Step by Step columns). The main difference is the constructor:

  public SwingForumClientComSocketImpl (URL u) {
    serverURL = u;
  }


The SwingForumClientComSocketImpl implements the SwingForumClientCom interface, which enforces implementation of the Forum 1.0 API.

Using 1.1 events

The Swing Forum uses the 1.1 event model to define event handling for the trees as well as the other elements in the user interface.

Recall that the 1.1 event model is based on adding event listeners to objects that produce the events. For example, a JButton produces an ActionEvent when it's clicked. The following statement adds an ActionListener to a JButton called jb:

  // jb is an instance of JButton
  jb.addActionListener (jbListener);


jb's event code calls the actionPerformed(...) method of jbListener every time the button is clicked.

But how do we get this instance of jbListener? We could go the standard route -- define a new class that implements the ActionListener interface and then pass a new instance of this in the add call above. This works, but the standard approach doesn't take advantage of a handy 1.1 shortcut -- the anonymous class. This technique lets us do it all at once, plus define an inner class that has access to class variables and methods.

  jb.addActionListener (new ActionListener () {
    public void actionPerformed (ActionEvent e) {
      // do something here - enclosing scope enabled
    }
  });


We'll make use of this technique extensively in the Swing Forum.

Additions to the SwingForum class

Now, time for the code.

The following sections of code have been added to the SwingForum code we developed last time.

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

public class SwingForum extends JFrame {
static final String QUOTE_CHARS = "> ";


This section is almost identical to the original SwingForum. Along with several more import statements, the QUOTE_CHARS definition is added to specify the characters that are prepended to each line of existing text in a reply.

    replyAction = new AbstractAction ("Reply") {
      public void actionPerformed (ActionEvent e) {
        quoteReply ();
        display.setSelectedComponent (replyTab);
      }
    };

refreshAction = new AbstractAction ("Refresh Server") { public void actionPerformed (ActionEvent e) { loadAllThreads (); } };


These are the only two action definitions that are affected by the addition of the new functionality. When the reply action occurs, the replyArea is reset by the quoteReply() method to contain reply text quoted with the QUOTE_CHARS.

When the refresh action occurs, the client rereads the threads from the server by calling the loadAllThreads() method.

  JTree tree;
  JScrollPane treeScroller;
  DefaultTreeModel treeModel;

void layOutTree () { tree = new JTree (); tree.getSelectionModel ().setSelectionMode ( TreeSelectionModel.SINGLE_TREE_SELECTION); treeScroller = new JScrollPane (tree); loadAllThreads ();
// load threads for root || arts for thread tree.addMouseListener (new MouseAdapter () { public synchronized void mouseClicked (MouseEvent e) { TreePath path = tree.getPathForLocation (e.getX (), e.getY ()); if (path != null) { DefaultMutableTreeNode node; node = (DefaultMutableTreeNode) path.getLastPathComponent (); if (e.getClickCount () == 2) { if (node.isRoot ()) { loadAllThreads (); tree.setSelectionPath (new TreePath (treeModel.getRoot ())); } else if (((DefaultMutableTreeNode)node.getParent()).isRoot()) { loadThreadArticles (node); tree.setSelectionPath (path); tree.expandPath (path); } } } } });


The layOutTree method is the first of two 1.1 event adapters that we define to handle populating the tree with threads and articles. This adapter listens to mouse clicks and gets a TreePath using the x and y position of the click. If the path isn't null, the logic gets the last component of the path, which represents the node that has just been clicked.

If the user double-clicks the mouse, the method will load either the threads from the server or the articles in a thread, depending on whether or not the double-click was on the root level or the second level of the tree.

After layOutTree loads the threads or articles, it selects the item that was double-clicked and expands the path to it so that it's visible in the display.

    // display art if selected. ignore root and thread selection
    tree.addTreeSelectionListener (new TreeSelectionListener () {
      public synchronized void valueChanged (TreeSelectionEvent e) {
        TreePath path = e.getPath ();
        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode) path.getLastPathComponent ();
        if ((path.getPath ()).length > 2 ) {
          ForumArticle art = (ForumArticle) node.getUserObject ();
          readArea.setText (art.getText ());
          display.setSelectedComponent (readTab);
        } else {
          readArea.setText ("");
        }
      }
    });


This listener listens to the tree for selection events. Selection events occur whenever a user keyboards to or clicks on a node, or when a node is selected programmatically.

We're interested only in hearing selections that occur to nodes representing articles. Remember, we decided that double-clicks on the server and threads would be required to cause refreshes; otherwise, we would have put the refresh-from-server code in here instead of in a mouse listener.

Note that we could have used the mouse listener (rather than the one above) to drive selections by listening to single mouse clicks. The problem with this solution is that it wouldn't capture keyboard-driven selections.

    tree.addKeyListener (new KeyAdapter () {
      public synchronized void keyTyped (KeyEvent e) {
        if (e.getKeyChar () == '\u0012') {
          TreePath path = tree.getSelectionPath ();
          if (path != null) {
            DefaultMutableTreeNode node;
            node = (DefaultMutableTreeNode) path.getLastPathComponent ();
            if (node.isRoot ()) {
              loadAllThreads ();
            } else if (((DefaultMutableTreeNode) node.getParent ()).isRoot ()) {
              loadThreadArticles (node);
              tree.setSelectionPath (path);
              tree.expandPath (path);
            }
          }
        }
      }
    });


The only problem with our interface so far is that we still rely on double-clicks to refresh threads, so it's impossible to use the interface with a keyboard exclusively.

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources
  • Download this article and the complete source as a zip file http://www.javaworld.com/jw-11-1998/step/jw-11-step.zip
  • Download this article and the complete source as a gzipped tar file http://www.javaworld.com/jw-11-1998/step/jw-11-step.tar.gz
  • Thinking about buying a book on JFC/Swing technologies? Don't make a move until you read Laurence Vanhelsuwé's detailed review on the available books covering this craze http://www.javaworld.com/jw-09-1998/jw-09-bookreview.html
  • If you're interested in learning more about JTree, see Tomer Meshorer's "Swing's JTree component model" (JavaWorld, October 1998), which explores how to capture hierarchical structures using this new component model http://www.javaworld.com/jw-10-1998/jw-10-jtree.html
  • Read previous Step by Step columns by Shoffner and Hughes http://www.javaworld.com/topicalindex/jw-ti-step.html
  • Information about JFC and Swing
  • Sun's JFC home page http://java.sun.com/products/jfc/
  • JDK 1.2 beta 4 download page (registration required) http://developer.javasoft.com/developer/earlyAccess/jdk12/index.html
  • The Swing Connection http://java.sun.com/products/jfc/tsc/index.html
  • Java Plug-in home page http://java.sun.com/products/plugin/index.html
  • Java Plug-in documentation http://java.sun.com/products/plugin/1.1.1/docs/index.html
  • Details on Java Plug-in and HTML for NS and IE http://java.sun.com/products/plugin/1.1.1/docs/tags.html
  • Credits
  • Clip art used in the Swing Forum http://www.iconographics.com/