Develop Visual Basic-style techniques to applet design -- convert temperatures, too!
Java has a very traditional framework for laying out and implementing user interfaces. If you are familiar with the event loop type of programming you would do in an X11 or a Windows application, you will feel pretty much at home with Java. But (and there wouldnโt be any point in writing this if there wasnโt a โbutโ in there somewhere) Java is not typically running in a โtraditional environment.โ
The environment that applets find themselves running in is already rich with expressive power for laying out documents. Why not do that with applet applications? You use tables to lay out forms, donโt you?
To take advantage of this unique position that applets find themselves in, I set out to develop some building-block applets that could be combined into unique applications. To some extent this idea is โstolenโ from Visual Basic, but in truth it bears nothing in common with that system, only a common way of explaining some of its features so that if you are familiar with Visual Basic then you can understand what these applets are trying to do. However, Iโll start by looking at the traditional interface before jumping into this new one.
In nearly all modern window systems there is a notion of a containing window or frame and some subsidiary controls, which are sometimes called widgets. The basic application that controls such an application is written, at a gross level, to be something like:
begin
initialize window system, create base window.
Create some controls, add them to the window.
Open up the window.
while ( not done ) do
Get an event from the window system.
switch on EVENT:
case MENU
do menus.
case SELECT
do select.
case KEYBOARD
do keyboard.
case I/O
do I/O.
case EXIT
leave.
end while.
end application.
One of the disadvantages of writing a window-based tool in this way is that it doesnโt necessarily take advantage of threads easily. Another disadvantage is that if the subroutines for handling events are โslowโ getting back to the event loop, response can be problematic. Finally, it can be very difficult to generate โsyntheticโ events โ events that are generated as a computed result of a userโs interaction, not necessarily direct user interaction.
So what is different about the applet environment? First, the window in which the applet operates already exists. The browser has created it for you and is already dealing with input from it. Secondly, applets today mostly live in WWW browsers, which already know how to lay out pages based on formatting codes that the user has input.
How might one take advantage of this? I did it by designing a collection of applets that were each a single โcontrolโ running in its own thread, and communicating with other controls using inter-Thread data channels. (So you see, part 1 and part 2 of this series were not a waste of time.) At the highest level these applets appear as individual items on the page, so you can lay them out with HTML tags like you would lay out images or forms. However, unlike forms, applets can interoperate without constraint. A typical form might appear in HTML as follows:
<html><head><title>Figure 1. Communicating Applets</title></head>
<body>
<h2>Communicating Applets</h2>
<table class="legacyTable">
<tr>
<td rowspan=2>
<applet code="Slider.class" width=21 height=100>
<param name="datachannel" value="slider1">
<param name="orientation" value="vertical">
</applet>
</td><td align=left>
<applet code="Numeric.class" width=30 height=21">
<param name="datachannel" value="slider1">
<param name="minvalue" value="32">
<param name="maxvalue" value="212">
</applet>
</td>
</tr>
<tr>
</td><td align=left>
<applet code="Numeric.class" width=30 height=21">
<param name="datachannel" value="slider1">
<param name="minvalue" value="0">
<param name="maxvalue" value="100.0">
</applet>
</td>
</tr>
</table>
</body>
</html>
Depending on how well you read raw HTML, you may see that this document is organized as two columns. In the left column is an applet of type Slider and in the right column are two applets, both of type Numeric. Iโll cover what they do next, but the interesting bit here is that they are automatically layed out with the two numeric applets to the right of the slider applet.
Note: As of this writing, the release version of Netscape Navigator 2.0 has a serious bug where applets that are embedded in tables like the one above will not operate correctly if the page is left and then revisited via the back or reload button. So the above HTML works exactly once when you load the page.
If you are using these applets a lot, you may wish to put them in a page outside of a table so that they will work correctly at all times.
The Slider class
The next step is to look at the applets. The simpler one is probably the Slider applet. The code for this applet is shown below. However, large sections that simply do graphics have been excised for the sake of clarity and brevity.
import java.applet.Applet;
import java.awt.*;
import util.comm.*;
public class Slider extends Applet implements Runnable {
Thread updater = null;
DataChannel rr = null;
Image altImage = null;
Graphics offscreen = null;
final static int SLIDER_RESOLUTION = 2000;
final static int HORIZONTAL = 1;
final static int VERTICAL = 2;
int myOrientation;
int currentPosition = SLIDER_RESOLUTION / 2; // midpoint
String myChannel;
The preamble of the applet defines the variables that are used in each instance. Of particular interest is the variable myChannel, which contains a string that will identify the data channel associated with this slider, and the variable rr, which is the DataChannel object. Because this applet wants to have flickerless animation of the slider, it uses an offscreen image and graphics context.
public void init() { ... }
The first public interface in the applet is the implementation of init. This function collects parameters from the HTML code. The only two parameters of interest above are orientation, which can take on the value horizontal or vertical, and datachannel, which contains the name of the data channel that this slider should write to.
The slider display
The slider is displayed as a box with two square end caps and a rectangular elevator that moves between the two ends. This causes it to look a lot like a Windows 95 slider.
void drawSquare(Graphics g, int x, int y, int size) {...}
void leftCap(Graphics g, int x, int y) {...}
void rightCap(Graphics g, int x, int y) { ... }
void topCap(Graphics g, int x, int y) { }
void bottomCap(Graphics g, int x, int y) { ... }
void elevator(Graphics g) { ... }
The first few methods, shown above, are helper methods. These methods draw the basic components of a slider, the end caps, and the elevator. Note that while the end caps take an X and Y coordinate, the elevator determines its position based on the current position.
1 public void update(Graphics g) {
2 if (offscreen == null) {
3 altImage = createImage(myWidth, myHeight);
4 offscreen = altImage.getGraphics();
5 }
6 offscreen.setColor(bgColor);
7 offscreen.fillRect(0, 0, myWidth, myHeight);
8 offscreen.setColor(Color.black);
9 rect3D(offscreen, 0, 0, myWidth, myHeight, 2, false);
10
11 if (myOrientation == HORIZONTAL) {
12 leftCap(offscreen, 0, 0);
13 rightCap(offscreen, myWidth - 21, 0);
14 } else {
15 topCap(offscreen, 0, 0);
16 bottomCap(offscreen, 0, myHeight - 21);
17 }
18 elevator(offscreen);
19 g.drawImage(altImage, 0, 0, null);
20 }
This is the update function. The function initializes the offscreen graphics context and then sets up by filling it with the background color. Then, depending on the orientation, it draws either left and right end caps or top and bottom ones, then it draws the elevator. Finally it writes the offscreen image. All in all, a very basic update function. I override the update method rather than paint since the default update method clears the background before drawing, which leads to flashing.
Next comes the applet version of the start method:
1 public void start() {
2 updater = new Thread(this);
3
4 rr = DataChannel.getChannel(myChannel, updater, 8);
5 updater.start();
6 }
start creates a new thread using the current object instance as its basis. Then it creates a link to the named data channel. And then to finish up, it starts the new thread running.
1 public void stop() {
2 rr.releaseChannel(updater);
3 updater = null;
4 }
stop is pretty simple, too. Its primary job is to release the data channel and to null out the pointer to the updater thread. This latter item is necessary if the thread object created by start is to be later collected by the garbage collector.
The most critical method as far as the thread is concerned is run. In the slider applet, the run method is responsible for monitoring the data channel and updating the position of the elevator based on what should be the current position.
1 public void run() {
2 while (true) {
3 try {
4 currentPosition = ((Integer) rr.getValue()).intValue();
5 } catch (DataChannelOverrun e) {
6 System.out.println("Slider: OVERRUN!");
7 } catch (DataChannelShutdown e) {
8 System.out.println("Slider thread shutting down.");
9 return;
10 } catch (DataChannelException ee) {
11 System.out.println("We're dead.");
12 ee.printStackTrace();
13 }
14 currentPosition = Math.max(0, currentPosition);
15 currentPosition = Math.min(SLIDER_RESOLUTION, currentPosition);
16 repaint();
17 }
18 }
You might ask yourself, โWhy should the slider update its appearance from the data channel? Doesnโt it already know where it is supposed to be?โ The answer is simplicity of implementation and the desire to have other things update the sliderโs position.
The implementation of the run method is very straightforward. The thread sleeps when it calls the getValue method of the data channel. It assumes that the value coming out of the channel will be an object of type Integer. If it isnโt, the thread will bomb out so one enhancement would be to put a check to make sure an Integer was received. The thread will wake up because it got a value or an exception. The data channel exceptions were covered in the previous article so I wonโt go over them here. Suffice it to say that should the thread receive a shutdown exception it will exit. Finally, the value returned is clipped to be a legal value between 0 and SLIDER_RESOLUTION. This prevents the elevator-drawing method from going berzerk. The last step of the method is to call repaint, which causes the elevator location to be updated in the applet.
The slider user interface
The mouse event functions are used to implement the slider user interface. While it could have been fairly sophisticated, the current implementation isnโt. The interface is as follows:
- Click an end cap and the elevator moves 2.5% closer to that end cap;
- Click above or below the elevator and the elevator moves to that point;
- Click and drag the elevator and the elevator follows the mouse.
First there is a helper routine called newPosition, which is shown below.
1 void newPosition(int x, int y) {
2 int loVal, hiVal, midRange, sample;
3
4 // zero is when the midpoint of the elevator is against the bottom
5 loVal = 18 + (elevatorSize/2); // lowest value
6 hiVal = ((myOrientation == HORIZONTAL) ? myWidth : myHeight) -
7 18 - (elevatorSize/2);
8 midRange = hiVal - loVal;
9 sample = Math.max(((myOrientation == HORIZONTAL) ? x : y), loVal);
10 sample = Math.min(sample, hiVal);
11 rr.putValue(new Integer(((sample - loVal) * SLIDER_RESOLUTION) / midRange));
12 }
This method uses the midpoint of the elevator as the control point for the slider. Using the midpoint ensures that the slider will reach its maximum and minimum values in an intuitive way. As in the run method, the resulting value is clipped to keep it in the range of 0 to SLIDER_RESOLUTION.
Next the applet processes mouse down events (clicks).
1 public boolean mouseDown(Event ev, int x, int y) {
2 int result = currentPosition;
3 if (((myOrientation == HORIZONTAL) && ((x <= 20) && (x >= 0))) ||
4 (((myOrientation == VERTICAL) && (y <= 20) && (y >= 0)))) {
5 result -= (SLIDER_RESOLUTION * .025);
6 if (result < 0) result = 0;
7 rr.putValue(new Integer(result));
8 return true;
9 }
10 if (((myOrientation == HORIZONTAL) && ((x >= (myWidth - 20)) && (x <= myWidth))) ||
11 (((myOrientation == VERTICAL) && (y >= (myHeight - 20)) && (y <= myHeight)))) {
12 result += (SLIDER_RESOLUTION * .025);
13 if (result > SLIDER_RESOLUTION) result = SLIDER_RESOLUTION;
14 rr.putValue(new Integer(result));
15 return true;
16 }
17 return true;
18 }
Lines 3 through 9 capture the case when the left or upper end cap is clicked, and lines 10 through 16 do the same for the right or lower end cap. In either event the current position of the slider is updated and then the method returns. Note that if the applet did not use the data channel to update itself, this method would call repaint before returning.
To implement the drag behavior, the applet has to monitor drag events. This is done with the mouseDrag method below:
1 public boolean mouseDrag(Event ev, int x, int y) {
2 if ( (myOrientation == HORIZONTAL) &&
3 ((y <= myHeight) && (y >= 0)) &&
4 ( ((x <= (capSize + 2)) && (x >= 0)) ||
5 ((x >= (myWidth - capSize - 2)) && (x <= myWidth)) ) ) {
6 return true;
7 }
8 if ( (myOrientation == VERTICAL) &&
9 ((x <= myHeight) && (x >= 0)) &&
10 ( ((y <= (capSize + 2)) && (y >= 0)) ||
11 ((y >= (myHeight - capSize - 2)) && (y <= myHeight)) ) ) {
12 return true;
13 }
14 newPosition(x, y);
15 return true;
16 }
As you can see, lines 2 through 7 and 8 through 13 check to see if the mouse is inside one of the end caps. In the case of dragging the elevator, drags that occur inside the end caps are ignored. This prevents the elevator from jumping to the end cap when the end cap is clicked on.
The Numeric class
The other applet on the page is the Numeric applet. In some ways this applet is simpler than a slider because it doesnโt have a user interface per se, it merely reflects a value to the screen. Again, the code is shown in outline form to save space. Further, the actual Numeric class has features that are going to be used in the next part and they are not discussed either, for now it is simply a box with a number in it.
The Numeric class has one significant instance variable and that is currentValue. This is represented as a long (64-bit) integer:
1 public class Numeric extends Applet implements Runnable {
2 Thread updater = null;
3 long currentValue = 0;
4 DataChannel myChannel = null;
5 double minValue, maxValue;
6 Image altImage = null;
7 Graphics offscreen = null;
8 Font numberFont = new Font("Courier", Font.BOLD, 14);
In addition to the current value, the indictator maintains a notion of a minimum and maximum value. These are used to some advantage as we will see in a minute. Also the font used is coded as 14-point Courier for convenience but could be anything, even dynamic if you chose.
As in the slider, the init method fetches the parameters for the applet. In this case the ones of interest are the data channel to monitor and the minimum and maximum values. To facilitate getting values from the parameters, I wrote a simple helper method that fetches a value, returns it as a double, and returns a default if there were any errors. There are in fact several such helper functions but they all share the basic structure of the getDouble method shown below:
1 double getDouble(String parmName, double defaultValue) {
2 double res;
3 String val = getParameter(parmName);
4 if (val == null)
5 return defaultValue;
6 try {
7 res = (Double.valueOf(val)).doubleValue();
8 } catch (NumberFormatException e) {
9 res = defaultValue;
10 }
11 return res;
12 }
Basically line 5 returns the default if the parameter was not present and line 9 sets the result to the default if the number in the value tag of the parameter could not be parsed as a valid double value.
The display of the result depends on a method named formattedValue. It simply returns a string for the current value, however it has the ability to treat the current value as a fixed-point number and that takes a bit of coding. For now you can think of it as simply providing a right-justified string containing the current value.
Displaying the value is accomplished by the update method which, in pseudo code, consists of:
1 public void update(Graphics g) {
2 if (offscreen graphics not initialized)
3 initalize offscreen graphics.
4 Clear graphics.
5 Draw a "sunken" box for the numbers.
6 Convert the numbers into a string.
7 Draw the numbers into the sunken box.
8 Draw the result out to the display.
9 return;
10 }
The actual implementation is more complicated but not terribly so. The start and stop methods are identical to the methods in the slider case as they have the exact same requirements: starting a thread to monitor the data channel.
The run method is also the same as in the slider case except that the current value is mapped from a number between 0 and 2,000 into a number between minValue and maxValue. This is done using the equation:
currentValue = ((maxValue - minValue) * sample / 2000) + minValue;
As you can see this assumes that the indicator is being fed from a data channel created by a slider.
There are no user-interface routines in the Numeric class since it doesnโt take any input. Should it be updated to accept typed-in characters then that would be handled by the overriding the keyDown method.
Putting it all together
Now that you know how the applets work, you have probably figured out what this page does, it displays a Fahrenheit to Celsius conversion. The interesting thing about the conversion is that the conversion constants are computed implicitly by the Numeric class instances.
The slider delivers a value between 0 and 2,000, the Numeric objects convert that in the first case to a number between 32 and 212 (Fahrenheit) and in the second case to a number between 0 and 100 (Celsius). The structure of the page is set up to solve a pair of simultaneous equations:
Tf = (p/2000) * x + 32
Tc = (p/2000) * x
Where Tf is the temperature in degrees Fahrenheit and Tc is the temperature in degrees Celsius.
With a few modifications the page can become more attractive, and its function becomes obvious. See the HTML below:
1 *** <html><head><title>Figure 2. Temperature Conversion</title></head>
2 <body>
3 *** <h2>Temperature Conversion</h2>
4
5 <table class="legacyTable">
6 <tr>
7 <td rowspan=2>
8 <applet code="Slider.class" width=21 height=100>
9 <param name="datachannel" value="slider1">
10 <param name="orientation" value="vertical">
11 </applet>
12 *** </td><td align=right>
13 *** Fahrenheit
14 </td><td align=left>
15 <applet code="Numeric.class" width=30 height=21">
16 <param name="datachannel" value="slider1">
17 <param name="minvalue" value="32">
18 <param name="maxvalue" value="212">
19 </applet>
20 </td>
21 </tr>
22 <tr>
23 *** <td align=right>
24 *** Celsius
25 </td><td align=left>
26 <applet code="Numeric.class" width=30 height=21">
27 <param name="datachannel" value="slider1">
28 <param name="minvalue" value="0">
29 <param name="maxvalue" value="100.0">
30 </applet>
31 </td>
32 </tr>
33 </table>
34
35 </body>
36 </html>
This is then a mini application that is written in HTML with Java โcontrols.โ
In the next part of this series, we enhance the Numeric class with a control channel and add some new controls to widen the scope of our โVisual Javaโ applications.


