| |||||||
![]() |
|||||||
What's So Hot about Java? (Page 2 of 3)
|
|||||||
Five Steps to Creating an Applet
Getting back to our helloWorld example, use Figure 2.2 and the following steps to build your helloWorld applet.
ADDING SOME BUZZ
By now, we have a simple applet and HTML example, and we understand the basics of applet flow and using the API documentation. Now, let's add some buzz to our applet; let's add some animation. Earlier, when we tested the Animator demo, we discussed the three basic ways of creating animations. For our helloWorld applet, we'll be displaying a series of images. So you don't have to generate your own images, just use the commands in Figure 3 to copy the images from the Animator demo, for a series of 10 (0-9) image files.
Since we know the animation is just displaying a series of images, we know we need to add a loop somewhere. But where? Obviously, not in init(), because our program would then stay in init() forever. Putting it in paint() doesn't make much sense, either. In fact, our proposed loop doesn't really fit anywhere in the applet flow init(), start(), stop(), destroy() that we discussed. What we really need is a way to just spawn off our proposed loop. Sounds like a thread, right? So, to add animation, we really need to add multithreading.
Since multithreading is implemented at the language level in Java, the Thread class is a member of the java.lang package. Use the API documentation to view the Thread class in the java.lang package. Notice that like the Applet class, the Thread class has start(), stop(), and destroy() methods we can override. It also has a run() method that is called by start(). Good. So let's put our loop in the run() method. Java doesn't implement multiple inheritance, so if we chose to have helloWorld extend Thread instead of Applet, we'd lose our interface to the browser. Not so good.
This is where Interfaces come in. Java provides Interfaces as templates of behavior, just as a class is a template. But Interfaces pass only method descriptions to their children. If you are still in the API documentation for the Thread class, notice that it implements the Runnable Interface. Click on Runnable, noting that Runnable defines only an abstract run() method. Okay. If helloWorld continues to extend Applet (so it keeps its interface to the browser) but also implements Runnable (so it adds the run() method), we have a place to put our loop. Perfect!
Now we know that we can access Java's multithreading capabilities in two ways. We can either define our class as a subclass of Thread, or we can implement the Runnable interface. Figure 4.1 shows our modified version of helloWorld, which now implements the Runnable interface.
(Ignore the big "1." It's not part of the code, so we'll explain it later.) Notice that we've also added a run() method, which has our proposed loop. Let's take a closer look at this loop in the run() method. See how it uses repaint() instead of paint() to paint the screen? The difference between repaint() and paint() is that repaint() calls paint(). Repaint is called whenever the image or window is changed or a new area is exposed. Repaint() clears the previous image from the screen, then calls paint() to actually paint the image on the screen. Since we want to cycle through a series of images, we used repaint() to clear, then paint the screen.
We don't want our images to zoom by too fast, so we've added a sleep(), so there will be a slight pause each time an image is displayed. You've probably also noticed that sleep() is enclosed in a try statement, because it's possible for an error to occur while sleep() is processing. In Java, the correct terminology is "The sleep method can throw the exception InterruptedException." The try statement says that Java should try to sleep, but if it's interrupted before the sleep() completes, it should do whatever is specified in the catch statement. The Java compiler tries to protect processes from stumbling into errors that programmers didn't consider. If a method throws an exception, the Java compiler requires you either to catch the exception (using the try statement) or to pass it up to the caller (using the throw clause). Notice that our catch statement does nothing. That's okay. At least we made the conscious decision not to do any error handling. In later examples, we'll handle exceptions.
Painting the Picture
Notice that we've added a start() and stop() method, so that when users go to the hello.html page, the animation thread will be started. When they leave the hello.html page, however, our thread won't waste cycles by displaying images that no one will see. If the user comes back to our hello.html page, the browser will restart our animation thread.
When you finish updating the
helloWorld . java file, compile it
(javac
helloWorld . java).
Use the browser to open the hello . html file, but don't forget to stop and restart the browser each time you update the applet. Our tiny applet now has multithreaded animation buzz! But wait a minute here. Why does the little man (Duke) sometimes paint so slowly? And why does the "Hello World!" message seem to flicker? Remember, the repaint() method clears the screen and then paints the image, so each pixel used to display the "Hello World!" message is set to grey (the background color), then to black, each time the image is repainted. We perceive this set and reset as a flicker.
Five Ways to Reduce Flicker
We'll cover these in order. First, it's important to realize that paint() will be called frequently. Its only responsibility should be to draw the image. Because our helloWorld applet is so simple, it was easy to limit our paint() to simply drawing the image. As your applets become more complex, try to precalculate as much as possible. For example, PC game programmers often use lookup tables, instead of calculating trigonometric functions, for exactly this reason.
Next, clear only what is necessary to help minimize the paint area. In our applet, there's really no reason to clear the area where the "Hello World" message is painted, as the message never changes. Since the repaint() method calls the update() method to clear and then paint() the screen, we can clear and paint only specific areas by overriding the update() method.
To test this, try inserting the update() method (Figure 5) into our new helloWorld class (the big "1" in Figure 4.2). Recompile and run. Notice that although Duke still flickers, the "Hello World!" message seems pretty steady. That's because, instead of clearing the entire image, we clear and paint only the part where Duke is drawn. To minimize the paint area even more, we'd need to use clipping. Clipping is beyond the scope of this article, but is explained in Lemay and Perkins' Teach Yourself Java in 21 Days.
The fourth method, making sure images are loaded before calling paint(), is really noticed only at the start of the animation. This is because the getImage() method called in init() returns immediately; image loading takes place in the background. In other words, the image may not be loaded when paint() is called. Java loads images asynchronously, so if an image is not actually needed until later, the user won't be unnecessarily delayed.
If you noticed the images painting slowly the first time, or if it looked like the motion was jerky at first (because some images were being skipped), you can use Java's MediaTracker to ensure images are loaded before they are painted. This can cause a delay before the animation is started, but once the animation does start, it'll be smoother.
To test this, try replacing the init() method in Figure 4 with the init() method in Figure 6. Be sure to recompile helloWorld.java and restart the browser to invoke the new version. You probably noticed the delay before the animation started, so bear in mind the tradeoff associated with pre-loading images. Note that the wait doesn't have to be in init(). If you choose to use MediaTracker, you can minimize delays by performing the wait directly before the animation is started.
Finally, double-buffering provides the best alternative for reducing flicker. Double-buffering takes advantage of the fact that it's faster to display a frame than to draw an image on the screen. Instead of drawing an image on the screen, the application draws the image to a frame that is not being displayed. Once the image is drawn, the application simply displays the frame where the image was drawn. To test this, make the following changes in helloWorld.java:
Panels, Widgets, EventsWe could certainly do a lot more with animations, but many other neat Java features await us, so let's move on to a new applet; we'll call it whiteBoard. Before starting on the whiteBoard applet, we need to discuss Java's Abstract Window Toolkit in more detail. In our helloWorld applet, we noted that because Applet was a subclass of Panel and Component, we could override inherited methods to draw on the browser window as if the page being displayed by the browser window was our applet's drawing board. As your applets become more sophisticated, you'll need more control over the layout of this "drawing board." You may already be aware that you can divide a Web page using HTML table and frame commands. Additionally, Java provides a Layout Manager so your applet can easily add panels, containers, and components within this frame or table entry. These panels and components can even be nested within one another. The browser communicates to these panels and components through events. Our whiteBoard applet will start off looking very similar to the DrawTest demo provided with the JDK, which allows users to draw pictures on their browser windows. This new applet will also give us practice with panels and events. In later sections, we'll add code to demonstrate frames, inter-applet communications, and socket communications. The resulting applet (shown in Figure 8 will let two users connect to a server and draw on a distributed whiteBoard. In other words, as either user draws on the whiteBoard, updates will be displayed on both users' browsers.Before we can start on our whiteBoard, we need to decide how the page will be formatted. DECIDING ON PAGE FORMATJava's Layout Manager includes Border, Card, Flow, Grid, and GridBag layouts. You can view an example of each layout in the API documentation by selecting the java.awt package, then selecting the class for each layout. Our whiteBoard applet's main component is the drawing board itself, but we'll also need a heading and controls. The BorderLayout suits our needs pretty well.We know the browser will send messages to our applet as events. Actions like pressing a key, pressing or releasing the mouse button, or dragging the mouse are all events. But pressing the mouse button on the whiteBoard itself should be handled differently from pressing the mouse button while in our controls area. We can separate these areas and events by making the drawing board one object and the controls a separate object. We'll refer to these as the whiteBoard Panel (wbPanel) and the Controls Panel (wbControls). You'll notice in Figure 8 that our resulting applet uses the FlowLayout for the Controls Panel, so we've nested a FlowLayout within a BorderLayout. Now, let's consider our wbPanel in more detail. We'll let users draw shapes with lines and circles, and we'll also let them enter text, but we'll start by just allowing lines. We'll need, however, to keep track of what is on the board, its location, and its color. We can simplify things by creating a Shape class. Because we don't know how many Shapes will be drawn, we'll use Java's Vector class to create a dynamic array of Shapes. In Figure 9, you'll see the Shape and the wbPanel classes. The Shape class includes a method named Shape. A method that has the same name as its class is called a constructor. Classes are to objects as blueprints are to houses. Constructors are methods that create an object from a class. An applet invokes a constructor by preceding the class name with the new operator. A constructor method initializes the new object and its variables and, in general, does anything the object needs to do to initialize itself. In the case of Shape, the constructor records the coordinates and color of the Shape. The keyword this simply refers to the object within which the this is used. For example, the Shape constructor is setting the coordinates and color of this instance of Shape to the values passed when its constructor is called. Now look at the wbPanel class. Notice that it also has a constructor. In addition, it has a method we haven't seen before, the action() method. The action() method is part of the Component class, and it's called whenever any action occurs within the component. Our whiteBoard will let users draw lines by pressing the mouse button (MOUSE_DOWN) at the starting point, dragging the mouse to the end point, then releasing the mouse button (MOUSE_UP). Each time repaint() is called for our wbPanel, the update() method will refresh the background, and paint() will redraw each shape. Figure 10 shows the whiteBoard.java file, which includes the whiteBoard and wbControls classes. Notice that the whiteBoard class itself is very simple. In fact, it includes only an init() method, which sets the layout, then creates and adds the heading, wbPanel, and wbControls components.The wbControls component is slightly more involved, because it needs to allow user input. Notice that by adding a Choice button to our wbControls panel, our action() method doesn't have to deal with MOUSE_DOWN events. Instead, the event is passed to the action() method as a Choice, with an argument specifying the user's selection. If you haven't already done so, create the wb.java (Figure 9) and whiteBoard.java (Figure 10) files, and then compile them with javac. You'll need an HTML file for the browser to open. Simply create a copy of the hello.html file created earlier and call it wboard.html (cp hello.html wboard.html). In wboard.html, replace helloWorld with whiteBoard and delete the height and width fields. To test, set the Location field of your browser to the wboard.html file.
|