Chapter 21

Creating User Interface Components

by David R. Chung

CONTENTS

The Java Abstract Window Toolkit (AWT) consists of classes that encapsulate basic GUI controls. Because Java is a multiplatform solution, the AWT provides a lowest common denominator interface. Any interface you develop should appear about the same on any platform. Often, the AWT is called Another Window Toolkit or (affectionately) Awful Window Toolkit. Chapter 16, "The Windowing (AWT) Package," provides an overview of the AWT.

Now don't be mislead-the AWT provides many useful controls and your applications or applets may not require anything more. Sometimes, however, an application needs something extra. This chapter examines two methods for creating custom user interface components: subclassing and combining controls.

Subclassing Controls

Subclassing is just a fancy object-oriented term for changing the way a class works. The actual method is to create a new class from the old one and add new features along the way. In this chapter, you learn how to extend the TextField class to create a password control. The new class will be a TextField that allows users to enter a password. Instead of displaying the password, the control displays asterisks as the user types.

The passField Class

You create the passField class by subclassing the TextField class. The control allows the user to type passwords. When the user enters a character, you catch the keyDown event. You keep track of the key the user pressed and place an asterisk in the control (to keep the password hidden).

Notice that this control does not actually verify that the password is valid. Instead, the control keeps track of user input and hides the actual characters. Later in the chapter, you see how to combine this control with others to create a useful password control.

Member Data

The passField class has to keep track of the characters entered by the user. To do so, we need a data member of type String:

class passField extends java.awt.TextField {

    String pass ;

The class constructor has to know how many characters the control can contain. To convey that information, the class constructor takes an integer parameter, chars. Because passField is derived from TextField, the first line in the constructor must be a call to the superclass constructor.

NOTE
To understand how classes are derived from (or extend) other classes, you must be familiar with some object-oriented terminology. In this example, passField is derived from TextField. TextField is referred to as the superclass, parent, or base class, while passField is referred to as the subclass, child, or derived class.

The call to super(int) actually calls TextField(int). The next line in the constructor simply creates the String:

    public passField( int chars ) {

        super( chars ) ;

        pass = new String() ;

    }

The reason for subclassing this control is to hide user input. To do this, the control must handle user input in the derived class before the TextField class does. To handle user input, this control overrides the Component class's keyDown() method. This method is called every time a key is pressed.

The overridden keyDown() method must do the following:

Because this class does not actually display the user-entered values, the call to getText() returns a String full of asterisks. Next, the method adds an asterisk to the String and puts it back in the control. The select() method is used to position the cursor at the end of the line; because the two parameters are the same, nothing is actually selected.

    public boolean keyDown( Event e, int key ) {

        String text = getText() ;
        setText( text + "*" ) ;
        select( text.length() + 1, text.length() + 1 ) ;

The next item of business is to store the keystroke in a String. Because the key parameter is an int, you must cast it to a char and then use the String.valueOf() method to convert it to a String. This new String is then concatenated onto the existing String. Finally, the keyDown() method has to return true because it has fully handled the keystroke.

        pass = pass + String.valueOf( (char)key ) ;

        return true ;
    }

The getString() method is provided to allow Containers that use this control to get the user-entered value:

    public String getString() {
        return pass ;
    }

}

To test the passField class, let's embed it in a simple applet, as shown here:

import java.awt.*;

public class testPassField extends java.applet.Applet {

    public void init() {
        add( new passField( 40 ) ) ;
    }

}

Figure 21.1 shows the testPassField applet.

Figure 21.1 : The testPassField applet.

If you run the applet and type some letters, you will see some strange behavior. When you press Backspace, instead of erasing an asterisk as you might expect, the control adds another one! The reason is when the keyDown() method gets a keystroke-any keystroke-it simply adds it to the string and displays another asterisk.

To fix this behavior, you must somehow handle certain keystrokes differently. One possibility is to add the following code to the beginning of the keyDown() method:

        if ( arg < 20 ) {
            return false ;
        {

This is only a partial solution, but it is a beginning. If the keystroke has an ASCII value less than 20 (that is, if it is a nonprinting character), then keyDown() should return false. If keyDown() or any other event-handling method returns false, the Event is passed on to the superclass method. In this example, this small modification causes the control to accept Backspaces. To really make this method useful, you must also change the value of the String to reflect any deletions.

Combining Controls

If you have ever served on a committee, you know how hard it is for a group of people to work together to reach a common goal. Without leadership, everyone seems to go their own way. Without well-coordinated communication, duplication of effort can occur. Likewise, if you try to put together a Java applet by combining several AWT controls, it may seem like you have a big committee-lots of activity but no leadership and no communication.

However, if you combine your controls into composite controls, they will then act like all the rest of the AWT controls. You can use the new composite controls anywhere you use regular AWT controls. To demonstrate composite controls, the following sections explain how to create a scrolling picture window control. This control takes an image and makes it scrollable. All the interaction between the AWT components that make up the control is handled internally. To use the control, all you have to do is create one and add it to your applet's layout.

Using Panels to Combine User Interface Elements

The base class for all composite controls is Panel. The Panel class allows you to embed other AWT components. This class is derived from Container, so it can contain user interface (UI) components. The Panel class also contains functions for managing embedded components.

Some functions in the Panel class can retrieve references to the embedded components. These functions allow the class to iteratively call methods in the embedded components. Other functions handle layout issues.

PANELS ARE COMPONENTS, TOO
The primary advantage of using Panel as your composite component base class is that it is a Component itself. Consequently, you can use your composite components like any other AWT components. You can take these new components and combine them to form composite components from other composite components and so on.
The new components can be added to layouts; they can generate existing events or create new ones. They are full-fledged UI components and can be used anywhere that AWT components are used.
The composite controls will be more versatile if you implement them with the appropriate layout manager. Because you want the controls to be self-contained, they should be able to lay themselves out properly no matter what size they are.

A Scrolling Picture Window Example

In this example, you create a scrolling picture window. You derive a class from Panel called ScrollingPictureWindow. The class contains three member objects: an ImageCanvas object (derived from Canvas) to hold the picture and two scroll bars.

This composite control provides a self-contained way of displaying a picture. A user simply has to pass an Image object to the control and the control does the rest. The control handles scrolling and updating the image. Figure 21.2 shows the scrolling picture window applet.

Figure 21.2 : The testPictureWindow applet.

The testPictureWindow applet uses a ScrollingPictureWindow object. The applet creates the ScrollingPictureWindow in exactly the same way that you would use an AWT control. The source code for the testPictureWindow applet is given in Listing 21.1.


Listing 21.1. The source code for the testPictureWindow applet.
import java.applet.*;
import java.awt.*;
import ScrollingPictureWindow ;

public class TestPictureWindow extends Applet {

    ScrollingPictureWindow pictureWindow ;

public void init() {

    Image img = getImage( getCodeBase(), "picture.gif" ) ;
    pictureWindow = new ScrollingPictureWindow( img ) ;
    setLayout( new BorderLayout() );
    add( "Center", pictureWindow ) ;

    }

};

The ImageCanvas Class

The ImageCanvas class is derived from Canvas. Canvas is provided in the AWT as a generic class for painting and drawing. You use this class to display your Image. The class contains one instance variable:

Image canvasImg ;

The ImageCanvas constructor takes an Image object as a parameter. Because object parameters are passed by reference, this makes a local reference to the Image object in the class:

public ImageCanvas( Image img ) {
    canvasImg = img ;
}

The only other method provided in the ImageCanvas class is paint(). The paint() method actually draws the image. Before doing any painting, however, the control has to determine whether its parent is enabled. This check allows the entire control to be turned off.

Because the picture scrolls, the class has to know where to draw it. The location of the image depends on the position of the scroll bars. In your scheme, the ScrollingPictureWindow object handles communication between the member objects. You have to query the ScrollingPictureWindow object to determine where to draw the image:

    public void paint(Graphics g) {

        if ( getParent().isEnabled() ) {
            g.drawImage( canvasImg,
              -1 * ((ScrollingPictureWindow)getParent()).imgX,
              -1 * ((ScrollingPictureWindow)getParent()).imgY,
              this ) ;
        }

    }

To get the information, use the getParent() method. The getParent() method is a member of the Component class. This method returns a reference to the Container object that holds the Component.

When you call getParent(), you get a reference to the ScrollingPictureWindow object. Because this reference is the Container type, you have to cast it to a ScrollingPictureWindow reference. Now you can access the public instance variables in the ScrollingPictureWindow object.

The imgX and imgY members contain the x and y coordinates of the point (in terms of the Image) that will be displayed in the upper-left corner of the window. If you want the point (10,5) to be displayed in the upper-left corner, pass -10 and -5 to drawImage().

Instance Variables

The ScrollingPictureWindow class contains several instance variables. These variables include the embedded controls and state variables. The embedded controls are stored as follows:

public ImageCanvas imageCanvas ;
Scrollbar   vertBar ;
Scrollbar   horzBar ;
Image       image ;

The last instance variable in this list is a reference to an Image object, which is passed in by the owner of your class object.

The remaining instance variables all contain information about the state of the control. The first two contain the size in pixels of the entire image:

int imgWidth ;
int imgHeight ;

The next two instance variables contain the current position of the image. These variables also reflect the current position of the scroll bars. Because the scroll bars and the image are tied together, both classes use these variables. The scroll bars set their values, and the ImageCanvas uses these values to place the image.

int imgX ;
int imgY ;

The last variable is used by the scroll bars. This value specifies the amount the scroll bar moves when you request a page up or page down.

int page ;

Class Construction

The class constructor performs all the initialization for your class. The constructor must do the following:

Initialize State Variables

Begin construction by setting the local Image reference to the Image argument:

    public ScrollingPictureWindow ( Image img ) {

        image = img ;

The next step in the construction process is simple. You have to initialize imgX and imgY to 0. What this really does is set the initial position of the image and scroll bars. These two instance variables contain the x and y offsets at which to display the image:

        imgX = 0 ;
        imgY = 0 ;

The ImageCanvas class needs these variables to determine how to place the image. The ImageCanvas paint() method accesses these instance variables directly and uses them in its call to drawImage().

Determine the Image Size

Your composite control has to know how large the image is. Once you have this information, you know it will remain constant. Unfortunately, determining the image size is not as straightforward as you may think.

Your class has been designed to take an Image object as a parameter, giving the users of the class a great deal of flexibility in loading the image any way they want. The image you receive may be one of many in an array. It may be in use by other objects in the applet. It may also have been just recently loaded by the calling applet. It is this last case that causes problems.

In your class constructor, it is possible that the reference you receive is to an image that is not yet fully loaded. To get the image size, you make a call to Image.getHeight(). If the image is not fully loaded, however, getHeight() returns -1. To get the size of the image, you must loop until getHeight() returns a value other than -1. Both while loops that follow have null bodies:

while ((imgHeight = image.getHeight(this)) == -1 ) {
    // loop until image loaded
}

while ((imgWidth  = image.getWidth(this)) == -1 ) {
    // loop until image loaded
}

Instantiate Member Controls

Next you must create the embedded member objects. The ImageCanvas takes the Image as a parameter. The scroll bar constructors each take a constant that determines whether the scroll bar is vertical or horizontal:

imageCanvas = new ImageCanvas( image ) ;

vertBar = new Scrollbar( Scrollbar.VERTICAL ) ;
horzBar = new Scrollbar( Scrollbar.HORIZONTAL ) ;

Set Up GridBagLayout

You use a GridBagLayout to lay out the embedded control. GridBagLayout is the most versatile layout manager in the AWT, and it provides precisely the control you need to arrange the components. GridBagLayout is the most powerful layout manager.

First you create a GridBagLayout object. Then you call setLayout() to make it the current layout manager:

GridBagLayout gridbag = new GridBagLayout();

setLayout( gridbag ) ;

Set Up Constraints for Each Control

The GridBagLayout class uses the GridBagConstraints class to specify how the controls are laid out. First, you create a GridBagConstraints object. Then you use the GridBagConstraints object to determine how to lay out the individual components:

GridBagConstraints c = new GridBagConstraints();

You add the ImageCanvas object to your panel first. Because the ScrollingPictureWindow control is supposed to act like the native AWT controls, it must be resizeable. You have to specify that the control can grow in both x and y directions, so you set the fill member to BOTH:

c.fill       = GridBagConstraints.BOTH ;

Because you want the image to fill all the available space with no padding, set the weight parameters to 1.0:

c.weightx    = 1.0;
c.weighty    = 1.0;

Finish laying out the image by calling setConstraints() to associate the ImageCanvas object with the GridBagConstraints object. Then add the image to the layout:

gridbag.setConstraints(imageCanvas, c);
add( imageCanvas ) ;

Next you lay out the scroll bars. Start with the vertical scroll bar. The vertical scroll bar should shrink or grow vertically when the control is resized, so you set the fill member to VERTICAL:

c.fill       = GridBagConstraints.VERTICAL ;

Look at your layout in terms of rows. You see that the first row contains two controls: the ImageCanvas and the vertical scroll bar. You indicate that the scroll bar is the last control in the row by setting the gridwidth member to REMAINDER.

c.gridwidth  = GridBagConstraints.REMAINDER ;

Complete the vertical scroll bar layout by associating it with the constraint object and then add it to the layout:

gridbag.setConstraints(vertBar, c);
add( vertBar ) ;

Finally, lay out the horizontal scroll bar. Because this scroll bar should be horizontally resizeable, set its fill member to HORIZONTAL:

c.fill       = GridBagConstraints.HORIZONTAL ;

The reason for using a GridBagLayout layout manager is to prevent the horizontal scroll bar from filling the entire width of the control. You want to guarantee that the horizontal scroll bar remains the same width as the ImageCanvas object. Fortunately, the GridBagConstraint class provides a means of tying the width of one object to the width of another.

You use the gridWidth member of the GridBagConstraint class to specify the width of the scroll bar in terms of grid cells. Set this member to 1 so that the horizontal scroll bar takes up the same width as the ImageCanvas object (they are both one cell wide). It is the ImageCanvas object that sets the cell size.

c.gridwidth  = 1 ;

The last thing you have to do is add the horizontal scroll bar. First associate it with the constraints object and then add it to the layout:

gridbag.setConstraints(horzBar, c);
add( horzBar ) ;

Depending on where your control is used, it may be resizeable. You handle resizing by over-riding the Component.reshape() method. This method is called every time a control is resized. The first thing that your function does is call the superclass's reshape() method. The superclass method does the real work of sizing. Because you are using a GridBagLayout, the LayoutManager resizes the individual components.

public synchronized void reshape(int x,
                                 int y,
                                 int width,
                                 int height) {

    super.reshape( x, y, width, height ) ;

You let the superclass do the resizing, so now you must update the image and scroll bars. First, determine whether the width of the control is greater than the image width plus the width of the vertical scroll bar. If the control width is greater, disable the horizontal scroll bar:

if ( width > imgWidth +
                   vertBar.preferredSize().width ) {

    horzBar.disable() ;

If the control width is not greater, enable the horizontal scroll bar:

} else {

            horzBar.enable() ;

Next, determine how to reposition the horizontal scroll bar. Start by getting the size of the entire control and the width of the vertical scroll bar:

lllll Rectangle bndRect = bounds() ;
int barWidth = vertBar.preferredSize().width ;

NOTE
When working with scroll bars, you have to set several values:
  • The thumb position
  • The maximum and minimum values
  • The size of the viewable page
  • The page increment

Now you can calculate the maximum value for the scroll bar. You always set the minimum of the scroll bar to 0. The maximum value is the image width minus the width of the ImageCanvas. You set the page size and page increment to one-tenth of the maximum size:

int max = imgWidth - (bndRect.width - barWidth);
page = max/10 ;

Before setting the new values, you must determine how to translate the old position to the new scale. Start by getting the old maximum value. If the old value is 0, you make the position 0:

int oldMax = horzBar.getMaximum() ;

            if ( oldMax == 0) {

                imgX = 0 ;

If the old maximum is not 0, you calculate the new position. First, express the old position as a fraction of the old maximum. Then multiply the fraction by the new maximum. The resulting value gives you the new position:

} else {

imgX = (int)(((float)imgX/(float)oldMax) *
                               (float)max) ;

}

The last thing you have to do is set the scroll bar parameters:

    horzBar.setValues( imgX, page, 0, max ) ;
    horzBar.setPageIncrement( page ) ;

}

Use the same algorithm to set the vertical scroll bar.

Event Handling

The scrolling picture window control is especially concerned about scroll bar events. All other types of events are passed on and handled outside your program.

You start by overriding the Component.handleEvent() method. In this method, you look for events generated by the horizontal scroll bar. If the event is one of the seven scroll bar events, you reset the imgX variable and call repaint(). You return true if you can handle the event.

public boolean handleEvent(Event e) {

        if ( e.target == horzBar ) {

            switch( e.id ) {

                case Event.SCROLL_PAGE_UP:
                case Event.SCROLL_LINE_UP:

                case Event.SCROLL_ABSOLUTE:

                case Event.SCROLL_LINE_DOWN:
                case Event.SCROLL_PAGE_DOWN:

                imgX = horzBar.getValue() ;
                imageCanvas.repaint();

                return true ;

            }

The code for handling the vertical scroll bar is the same as for the horizontal scroll bar. If you do not handle the event, call the superclass handleEvent() method and return:

    return super.handleEvent(e) ;

}

Putting It Together

You now have a composite control that can become a drop-in replacement for other AWT controls. It handles its own events and responds to external resizing. The complete ScrollingPictureWindow class appears in Listing 21.2.


Listing 21.2. The ScrollingPictureWindow class.
import java.awt.*;

public class ScrollingPictureWindow extends Panel {

    public ImageCanvas imageCanvas ;
    Scrollbar   vertBar ;
    Scrollbar   horzBar ;
    Image       image ;

    int imgWidth ;
    int imgHeight ;

    int imgX ;
    int imgY ;

    int page ;

    public ScrollingPictureWindow ( Image img ) {

        image = img ;

        imgX = 0 ;
        imgY = 0 ;


        while ((imgHeight = image.getHeight(this)) == -1 ) {
        // loop until image loaded
        }

        while ((imgWidth  = image.getWidth(this)) == -1 ) {
        // loop until image loaded
        }


        imageCanvas = new ImageCanvas( image ) ;

        vertBar = new Scrollbar( Scrollbar.VERTICAL ) ;
        horzBar = new Scrollbar( Scrollbar.HORIZONTAL ) ;

        GridBagLayout gridbag = new GridBagLayout();

        setLayout( gridbag ) ;

        GridBagConstraints c = new GridBagConstraints();

        c.fill       = GridBagConstraints.BOTH ;
        c.weightx    = 1.0;
        c.weighty    = 1.0;
        gridbag.setConstraints(imageCanvas, c);
        add( imageCanvas ) ;

        c.fill       = GridBagConstraints.VERTICAL ;
        c.gridwidth  = GridBagConstraints.REMAINDER ;
        gridbag.setConstraints(vertBar, c);
        add( vertBar ) ;

        c.fill       = GridBagConstraints.HORIZONTAL ;
        c.gridwidth  = 1 ;
        gridbag.setConstraints(horzBar, c);
        add( horzBar ) ;
    }

public synchronized void reshape(int x,
                                 int y,
                                 int width,
                                 int height) {

        super.reshape( x, y, width, height ) ;

        if ( width > imgWidth + vertBar.bounds().width ) {

            horzBar.disable() ;

        } else {

            horzBar.enable() ;

            Rectangle bndRect = bounds() ;

            int barWidth = vertBar.preferredSize().width ;

            int max = imgWidth - (bndRect.width - barWidth);
            page = max/10 ;

            int oldMax = horzBar.getMaximum() ;

            if ( oldMax == 0) {

                imgX = 0 ;

            } else {

            imgX = (int)(((float)imgX/(float)oldMax) *
                                           (float)max) ;
            }

            horzBar.setValues( imgX, page, 0, max ) ;
            horzBar.setPageIncrement( page ) ;

        }

        if (height > imgHeight + horzBar.bounds().height) {

            vertBar.disable() ;

        } else {

            vertBar.enable() ;

            Rectangle bndRect = bounds() ;

            int barHeight = horzBar.preferredSize().height ;

            int max = imgHeight - (bndRect.height - barHeight) ;
                                                
            page = max/10 ;

            int oldMax = vertBar.getMaximum() ;

            if ( oldMax == 0) {

                imgY = 0 ;

            } else {

            imgY = (int)(((float)imgY/(float)oldMax) *
                                           (float)max) ;
            }

            vertBar.setValues( imgY, page, 0, max ) ;
            vertBar.setPageIncrement( page ) ;

        }
    }

    public boolean handleEvent(Event e) {

        if ( e.target == horzBar ) {

            switch( e.id ) {

                case Event.SCROLL_PAGE_UP:
                case Event.SCROLL_LINE_UP:

                case Event.SCROLL_ABSOLUTE:

                case Event.SCROLL_LINE_DOWN:
                case Event.SCROLL_PAGE_DOWN:

                imgX = horzBar.getValue() ;
                imageCanvas.repaint();

                return true ;

            }


        } else if ( e.target == vertBar ) {

            switch( e.id ) {

                case Event.SCROLL_PAGE_UP:
                case Event.SCROLL_LINE_UP:

                case Event.SCROLL_ABSOLUTE:

                case Event.SCROLL_LINE_DOWN:
                case Event.SCROLL_PAGE_DOWN:

                imgY = vertBar.getValue() ;
                imageCanvas.repaint();

                return true ;

            }

}

        return super.handleEvent(e) ;

    }


};

class ImageCanvas extends Canvas {

    Image canvasImg ;

    public ImageCanvas( Image img ) {
        canvasImg = img ;
    }

    public void paint(Graphics g) {

        g.drawImage( canvasImg,
            -1 * ((ScrollingPictureWindow)getParent()).imgX,
            -1 * ((ScrollingPictureWindow)getParent()).imgY,
            this ) ;

    }
}

A Password-Protected Picture Control

The versatility of composite, or extended, controls becomes apparent when they are combined into a single applet. The applet presented in this section is a password-protected picture control. This control combines a passField control with a ScrollingPictureWindow control. Figures 21.3 and 21.4 show the testPassWindow applet.

Figure 21.3 : The testPassWindow applet before entering the password.

Figure 21.4 : The testPassWindow applet after entering the password.

The following applet code combines passField, ScrollingPictureWindow, and Button objects. Because the passField and ScrollingPictureWindow classes are self-contained, they are used here just like the AWT Button control. Because Java applets are reusable components themselves, the applet is like a new control itself. In fact, in Java, you create applets to embed in your Web pages the same way you create controls to embed in your applets.

import java.applet.*;
import java.awt.*;

public class testPassWindow extends Applet {

    passField              passwordField ;
    Button                 verifyButton ;
    ScrollingPictureWindow pictureWindow ;

The init() method creates the member objects and then places them in the control using a GridBagLayout (see Chapter 16, "The Windowing (AWT) Package," for details on GridBagLayout and the associated GridBagConstraints classes). Notice that the passField and ScrollingPictureWindow controls are used as if they were native AWT controls.

public void init() {

    GridBagLayout gridbag = new GridBagLayout();
    setLayout( gridbag ) ;

    {
    GridBagConstraints c = new GridBagConstraints();

    passwordField = new passField( 30 ) ;

    c.gridx      = 1 ;
    c.gridy      = 1 ;

    gridbag.setConstraints(passwordField, c);
    add( passwordField ) ;
    }

    {
    GridBagConstraints c = new GridBagConstraints();

    verifyButton  = new Button( "Verify" ) ;

    c.gridx      = 2 ;
    c.gridy      = 1 ;

    gridbag.setConstraints(verifyButton, c);
    add( verifyButton ) ;
    }

    {
    GridBagConstraints c = new GridBagConstraints();

    Image img = getImage( getCodeBase(), "picture.gif" ) ;
    pictureWindow = new ScrollingPictureWindow( img ) ;

    c.fill       = GridBagConstraints.BOTH ;
    c.gridx      = 1 ;
    c.gridy      = 2 ;
    c.gridwidth  = 2 ;
    c.weighty    = 1 ;

    gridbag.setConstraints(pictureWindow, c);
    add( pictureWindow ) ;
    }

    pictureWindow.disable() ;

    }

To tie all these controls together, it is necessary to handle Button events. In this example, the Applet class does the event handling. This combination of the password control and the picture window control is a good candidate for placement in a Panel so that it would become one big composite control.

    public boolean action( Event evt, Object arg ) {

        if ( evt.target instanceof Button ) {
            if ( ((String)arg).equals( "Verify" ) ){

                if ( passwordField.getString().equals(
                                              "secret" ) ) {

                    pictureWindow.enable() ;
                    pictureWindow.imageCanvas.invalidate() ;
                    pictureWindow.imageCanvas.repaint() ;

                    verifyButton.disable() ;

                    passwordField.disable() ;

                }
            return true ;
            }

        }

        return false ;
    }

};

Summary

Sometimes, your applets or applications require user interface functionality beyond what is provided by the AWT. You can use subclassing or composite controls to extend the AWT and create new classes from the basic AWT classes.

When you extend the AWT, you can create self-contained controls that respond to their own events. Your enhanced controls can often be used as drop-in replacements for associated AWT controls.

The passField class developed in this chapter is an example of a subclassed control. This class takes the basic functionality of an AWT TextField and enhances it. The result is a control that can be plugged in anywhere you might use a TextField.

The ScrollingPictureWindow class created in this chapter is a good example of a composite control. This class combines the techniques of subclassing and encapsulation. It also is a subclass of Panel and serves to encapsulate the Canvas control and two scroll bars.

When you design an applet or application in Java, you have at your disposal the basic AWT controls. Now you can create enhanced controls by combining and subclassing them. These new controls will become part of your personal Java toolbox and you can use them in all your future Java programming projects.