November 1997 |
![]() | Subscribe, it's free! |
Summary
Graphical applications programming has been greatly simplified with the advent of JDK 1.1. Specifically, the lightweight component framework allows transparent tools to be overlaid on each other, and the delegation event framework provides a much simpler event model.In this first of a two-part series, we'll look at implementing a simple whiteboard using these new features. Next month, we'll actually network the whiteboard to allow for multi-user collaboration over the Internet.
Of particular interest in this application is our use of transparent lightweight components to provide the whiteboard tools with simple access to GUI events and display. (3,000 words)
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
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 a 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 ()); else 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
Element
s 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 Element
s 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:
setDisplayList()
method keeps a reference to the
supplied display list. When the user draws a rectangle, we'll add a
RectElement
to this list.
getDisplay()
method returns this
. The
Rect
class is itself a transparent overlay component.
getControls()
method returns a new lightweight
panel, which includes a color selector for specifying the rectangle color.
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 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 Element
s with their
getTranslated()
methods.
Again, it is essentially a standalone AWT component that manipulates a
list of Element
s; 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 theaddUpdateListener()
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.)
Conclusion
As seems to be my standard operating procedure, I have flown through
a fairly large project in leaps and bounds. The main ideas are simple,
however:
Panel
everywhere.
A major goal of the implementation is to not tightly couple the
various classes of the application -- the only object common
among the various components of the whiteboard is the display
list. With this implementation, we can make radical changes to
the underlying whiteboard and the various tools will remain
oblivious. Furthermore, the controlling code (the WB
class) is trivial; all it does is switch among the different
tools.
Let me provide an example of the benefit of this design. To accommodate
multiple views of the whiteboard, all we have to do is create another
WBContainer
somewhere else, tied to the same display
list. No changes would be required to any of our existing code.
This approach is simply good object-oriented design. By applying these tehniques in your own applications, you will be able to easily extend and enhance them, with minimal changes to existing code.
Next month, we'll look at networking the code to enable distributed
users access to the same drawing space.
About the author
Adept in necromancy, Merlin was beguiled by the enchantress Nimue who
shut him up in a rock. Later, Vivien, the lady of the lake, entangled him
in a thornbush by means of spells, and there he sleeps, though his voice
may be sometimes heard.
Reach Merlin at merlin.hughes@javaworld.com.
Java Step by Step is a monthly column devoted to Java development. Each month, the authors of Step by Step will guide you through the development of a real-world Java application. Step by Step is co-authored by Michael Shoffner and Merlin Hughes, founders of Prominence Dot Com, a Java development firm in Chapel Hill, NC. Please send feedback and topic ideas to stepbystep@javaworld.com.
If you have problems with this magazine, contact
webmaster@javaworld.com
URL: http://www.javaworld.com/javaworld/jw-11-1997/jw-11-step.html
Last modified: