Draw the world: Create networked whiteboards with Java 1.1

Learn how to easily write a whiteboard using the new features of JDK 1.1

A whiteboard is a simple drawing utility, commonly supplied as part of a collaboration framework to allow distributed users to share a common drawing space. In this article, we will examine how JDK 1.1 simplifies the implementation of such an application.

Lightweight components, introduced by JDK 1.1, offer several important advantages over traditional JDK 1.0-style components. For one, they are much more efficient than traditional components because they do not make use of native GUI peers, being instead a pure-Java kluge on top of the old AWT. More importantly for this particular application, however, is that these components are transparent. This means that a lightweight component can easily overlay information on top of another component.

We will use this transparency feature to implement our whiteboard; when the user selects a particular drawing tool then a lightweight component will be overlaid on the whiteboard. The drawing tool can then use this component to annotate the whiteboard display and collect GUI events.

The whiteboard

Our whiteboard is perforce simple; the constraint of understandability requires that it be of fairly limited expanse. Hopefully, however, the techniques I demonstrate will be of fairly broad use, and will allow you to easily modify the application to suit your own needs.

The whiteboard in action

The following classes comprise the whiteboard:

  • WBLauncher -- A simple applet that launches the whiteboard when clicked.

  • WB -- The main whiteboard class, which is a standalone Frame containing all of the whiteboard components.

  • Tool -- An interface that provides access to all the whiteboard's drawing tools.

  • Element -- An interface that describes the elements of a whiteboard drawing.

  • WBContainer -- A lightweight container that displays the contents of the whiteboard.

  • Rect -- A drawing tool that allows rectangles of various colors to be placed on the whiteboard.

  • Select -- A drawing tool that allows existing elements of the whiteboard to be moved.

  • ObservableList -- A class that maintains the list of elements comprising a whiteboard drawing. It is very similar to a Vector except that it notifies listeners whenever a change is made to the list.

  • UpdateListener -- An interface that must be implemented by classes wishing to be notified when an ObservableList is modified.

  • UpdateEvent -- An event that notifies of a change to an ObservableList; it is delivered through the ObservableList interface.

  • LWContainer -- A generic lightweight container; a lightweight equivalent to the Panel class.

  • LWImageButton -- A generic lightweight image button.

This may seem like a large number of classes, but the core of the whiteboard is, in fact, surprisingly small. The WB class contains the controlling logic of the whiteboard; the WBContainer class contains the display code; and the Tool and Element interfaces describe the various tools and elements.

Stepping through the classes

For the sake of brevity, I will discuss the classes in general terms only, allowing you to read the source for a greater understanding.

Class WBLauncher

WBLauncher is a simple applet that provides a simple introductory interface to the whiteboard, delaying the download of the whiteboard's classes until the user chooses to launch it.

The whiteboard launcher

Because we are using the JDK 1.1 AWT, we must explicitly declare interest in GUI events in order to receive them. For this reason, we call the enableEvents() method in order to receive mouse events:

enableEvents (AWTEvent.MOUSE_EVENT_MASK);

These events are delivered through the new method hierarchy to our processEvent() method that can process the event as necessary:

protected void processMouseEvent (MouseEvent e) ...

In this next method, we use a Class.forName().newInstance() sequence to create an instance of the whiteboard. Using this mechanism, the whiteboard classes are only downloaded when the user actually clicks on the launcher:

Class theClass = Class.forName ("org.merlin.step.nov.WB");
wb = (Frame) theClass.newInstance ();

To initialize the whiteboard, we pass a dummy ActionEvent to its dispatchEvent() method with this as the source of the event; the whiteboard will then make use of our Applet methods. In particular, it can call getCodeBase() to determine its origin and can then download its configuration and image files:

wb.dispatchEvent (new ActionEvent (this, AWTEvent.RESERVED_ID_MAX + 1, ""));

Class WB

The WB class is a Frame subclass that sets up and controls the whiteboard. Under JDK 1.1, it is no longer necessary to subclass Frame in order to implement a freestanding application; however, you may find this approach to be convenient in some cases.

The whiteboard layout

As a component subclass, we must again call enableEvents() in order to receive events; in this case, window events:

enableEvents (AWTEvent.WINDOW_EVENT_MASK);

These window events will be passed to the processWindowEvent() method where we can handle the user closing the window:

protected void processWindowEvent (WindowEvent e) {
  super.processWindowEvent (e);
  if (e.getID () == WindowEvent.WINDOW_CLOSING)
    setVisible (false);

To trap the ActionEvent that the WBLauncher passes to initialize the whiteboard, we must also override the processEvent() method:

protected void processEvent (AWTEvent e) {
  if ((e instanceof ActionEvent) && (e.getSource () instanceof Applet))
    init ((Applet) e.getSource ());
    super.processEvent (e);

When the whiteboard is initialized, it downloads a simple configuration file. To parse this text file, we use the new Reader classes of JDK 1.1, which are more efficient, correct, and generalized for reading text than the InputStream classes. We use the character encoding "latin1", which stands for ISO Latin 1.

URL u = new URL (parent.getCodeBase (), "config.txt");
InputStreamReader i = new InputStreamReader (u.openStream (), "latin1");
BufferedReader r = new BufferedReader (i);
String line;
while ((line = r.readLine ()) != null) ...

The rest of the initialization simply lays out the user interface, which includes a row of tool icons on the left of the frame, a tool control panel at the bottom, and a whiteboard in the remaining space.

Clicking on any of the tool icons passes an ActionEvent to our actionPerformed() method. We then initialize the selected tool, display its control panel, and overlay its display component on the whiteboard, as shown here:

public void actionPerformed (ActionEvent e) ...

I'll discuss the whiteboard tools in more detail when we get to the Tool class.

Interface Tool

The Tool interface describes whiteboard drawing tools, and declares four methods: setDisplayList, getDisplay, getControls, and dispose. Let's take a look at each of these methods in more detail.

public void setDisplayList (ObservableList contents);

When a tool is initialized, this method is called to supply it with the current whiteboard display list (a list of Elements that make up the whiteboard drawing). The tool can manipulate the whiteboard by manipulating the elements of the display list.

public Component getDisplay ();

The getDisplay method returns a Component, which is overlaid on the actual whiteboard. The drawing tool can use this display surface to provide overlaid information (for example, partially drawn display elements) and to collect user-interface events, such as the user clicking on the whiteboard or dragging out a rectangle.

public Component getControls ();

The getControls method returns a Component or Container of control tools to configure the tool. The Component/Container is displayed at the bottom of the whiteboard.

public void dispose ();

We call dispose when another whiteboard tool is selected.

Two example tools, Rect and Select, are detailed later on.

Interface Element

The Element interface describes each drawing element in the whiteboard. Different Elements are typically created and added to the whiteboard by the various drawing tools.

For the purposes of networking, all drawing elements must be serializable; for this reason, we extend the Serializable interface:

public interface Element extends java.io.Serializable ...

Our whiteboard is fairly simple and provides only three methods in this interface: getBounds, paint, and getTranslated.

public Rectangle getBounds ();

The getBounds method returns a Rectangle describing the bounding box of the element.

public void paint (Graphics g);

The paint method is called when the element should draw itself to the specified graphics context, g.

public Element getTranslated (int dX, int dY);

Lastly, getTranslated returns a duplicate of the element, translated by the specified offset, which moves elements of the whiteboard.

Class WBContainer

The WBContainer class is a lightweight container that displays the current contents of the whiteboard. When a drawing tool is active, its transparent display Component is overlaid on this container.

When it is created, the WBContainer registers to be notified whenever the whiteboard display list is altered:

displayList.addUpdateListener (this);

Whenever an update occurs, we simply call repaint():

public void updateOccurred (UpdateEvent e) {
  repaint ();

The paint() method loops through the display list, calling each Element's paint() method. We use the bounding box of each element and the graphics clipping rectangle to make the repainting as efficient as possible.

Class Rect

Rect is a very simple whiteboard tool that allows the user to draw rectangles of various colors. It is actually composed of two classes: Rect (the actual tool), which implements the Tool interface, the transparent lightweight overlay component and the control panel; and RectElement, which is a rectangle Element.

We'll begin with the methods of the Tool interface:

  • The setDisplayList() method keeps a reference to the supplied display list. When the user draws a rectangle, we'll add a RectElement to this list.

  • The getDisplay() method returns this. The Rect class is itself a transparent overlay component.

  • The getControls() method returns a new lightweight panel, which includes a color selector for specifying the rectangle color.

  • The dispose() method is empty; there is nothing to do for this particular tool.

When the Rect tool is activated, it is automatically overlaid on the whiteboard. To receive AWT events it follows the standard procedure of calling enableEvents() and overriding the processEvent() method. Similarly, to draw, Rect simply provides a paint() method. We need not concern ourselves with drawing the rest of the whiteboard; this task is automatically handled by the WBContainer over which we are superimposed.

Writing the Rect tool is then simply a case of writing an AWT component; integration into the whiteboard is transparent.

Finally, once a completed RectElement is added to the display list, we're done. The WBContainer will be automatically notified of the change and will repaint itself appropriately.

Class Select

The Select tool is even simpler than the Rect tool, being just one class that implements the Tool interface. Select processes events in the normal manner, manipulating the display list using the simple methods that provide and move Elements with their getTranslated() methods.

Again, it is essentially a standalone AWT component that manipulates a list of Elements; it has no particular knowledge of the whiteboard into which it is integrated, as changes to the display list are automatically reflected by the WBContainer.

Class ObservableList

The ObservableList class is an observable Vector. Interested listeners register through the addUpdateListener() and removeUpdateListener() methods. Whenever an update is made to the list, the listeners will be notified.

For the time being, this class is implemented simply with an internal Vector. Next month, we will examine various mechanisms for networking the whiteboard with this class.

Interface UpdateListener

The UpdateListener interface is the mechanism by which listeners are notified of updates to an ObservableList.

public void updateOccurred (UpdateEvent e);

We call updateOccurred when an update is made to the list.

Class UpdateEvent

UpdateEvent notifies listeners when ObservableList has been updated. The event currently contains no payload of interest; a more involved implementation could indicate the particular change that occurred.

Class LWContainer

LWContainer is a simple minimal lightweight container; we simply extend Container and provide no additional methods. If Container were not abstract, this approach would be equivalent to using instances of the Container class itself.

LWContainer is much more efficient for layout purposes than the traditional Panel class. Because the class is pure Java, it does not require a corresponding native peer GUI component.

Class LWImageButton

LWImageButton is a simple lightweight image button: When the mouse moves over the image button, we highlight it; when the mouse button is pressed, we depress the image button; and when the mouse button is released, we fire an ActionEvent.

Because graphical Java buttons have been discussed in detail over the past few years (far too many times!), and because the transition to the JDK 1.1 event model follows the form that we have discussed in the past few episodes of Step by Step, I will not bore you with a dissection of the methods I used. (See the Resources section for links to discussions on this topic.)

1 2 Page 1
Page 1 of 2