TOC
BACK
FORWARD
HOME

Java 1.1 Unleashed

- 22 -
Creating User Interface Components

by David R. Chung

IN THIS CHAPTER

  • Extending Controls
  • Combining Controls
  • A Scrolling Picture-Window Example
  • A Password-Protected Picture Control

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. Chap-ter 14, "The Windowing (AWT) Package," provides an overview of the AWT.

Now don't be misled--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: extending controls and combining controls.

Extending Controls

In this chapter, you learn how to extend the TextField class to create a password control. The new class is a TextField that allows users to enter a password. Instead of displaying the password, however, the control displays asterisks as the user types.

The mechanism you use to extend the control is called subclassing. Subclassing is just a fancy object-oriented term for changing the way a class works. The actual process creates a new class from the old one and adds new features along the way.

The passField Class

You create the passField class by subclassing the TextField class. The new control allows the user to type passwords. You provide an implementation of the processKeyEvent() method for the class. When the user enters a character, processKeyEvent() responds and consumes the event. You keep track of the key the user presses 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 other methods 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, it requires a data member of type String:

import java.awt.*;
import java.awt.event.*;

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 class, or base class; passField is referred to as the subclass, child class, or derived class.

The call to super(int) actually calls the superclass constructor, TextField(int). The enableEvents() method indicates that this class processes keyboard events. The next line in the constructor simply creates the String:

public passField( int chars ) {
        super( chars ) ;
        enableEvents( AWTEvent.KEY_EVENT_MASK ) ;
        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. Events are then consumed so that the TextField base class does not respond to those events. To handle user input, this control overrides the class's processKeyEvent() method, which is called every time the user presses a key.

The overridden processKeyEvent() method must do the following:

  • Add an asterisk to the control.

  • Store the actual key value entered.

  • Position the cursor at the end of the string.

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:

protected void processKeyEvent( KeyEvent e ) {
        switch ( e.getID() ) {
            case KeyEvent.KEY_PRESSED :
                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 to the existing String. The processKeyEvent() method has fully handled the keyboard event. The call to consume() prevents any other method from responding to the event.

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

    }

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 22.1 shows the testPassField applet.

Figure 22.1.

The testPassField applet.

If you run the applet and type some letters, you will see that there are some strange behaviors. When you press Backspace, instead of erasing an asterisk as you might expect, the control adds another one! The reason is that when the processKeyEvent() 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. If you call consume() only for the keystrokes you want to handle, the superclass's processKeyEvent() method handles the other keystrokes. Replace the call to consume() with this code:

        if ( arg > 20 ) {
            e.consume();

        }

Although this is only a partial solution, it is a beginning. If the keystroke has an ASCII value less than 20 (that is, if it is a nonprinting character), it is not consumed and the superclass's processKeyEvent() method handles it. If your processKeyEvent() method consumes the event, the event is not passed on. In this example, this small modification causes the control to accept Backspaces. To really make this method useful, however, 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 that 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 (UI) Elements

The base class for all composite controls is Panel. The Panel class allows you to embed other AWT components in it. Because this class is derived from Container, it can contain 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 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 scrollbars.

To respond to the scrollbars, the ScrollingPictureWindow class implements AdjustmentListener. Therefore, the class is the listener object for its own scrollbars.

This composite control provides a self-contained way to display 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 22.2 shows the scrolling picture-window applet.

Figure 22.2.

The testPictureWindow applet.

The testPictureWindow applet uses a ScrollingPictureWindow object. The applet creates the ScrollingPictureWindow in exactly the same way you would use an AWT control. The source code for the testPictureWindow class is given in Listing 22.1. This class and the ScrollingPictureWindow class in Listing 22.2, found later in this chapter, make up the testPictureWindow applet. Both classes can be found on the CD-ROM that accompanies this book.

Listing 22.1. The source code for the testPictureWindow class.

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

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 ) ;
    pictureWindow.setEnabled( true ) ;
    }

};

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, img becomes 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 scrollbars. 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 scrollbars. Because the scrollbars and the image are tied together, both classes use these variables. The scrollbars set their values, and the ImageCanvas uses these values to place the image:

int imgX ;

int imgY ;

The last variable is used by the scrollbars. This value specifies the amount the scrollbar moves when you request a page up or page down:

int block ;

Class Construction

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

  • Initialize the state variables

  • Determine the size of the image

  • Instantiate the member controls

  • Set up the GridBagLayout layout manager

  • Set the constraints for each control

  • Add the class as the listener

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 zero. What this really does is set the initial position of the image and the scrollbars. 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 to load 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 scrollbar constructors each take a constant that determines whether the scrollbar is vertical or horizontal:

imageCanvas = new ImageCanvas( image ) ;

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

Set Up GridBagLayout

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

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. Because you have to specify that the control can grow in both x and y directions, 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 ) ;

Then you lay out the scrollbars. Start with the vertical scrollbar. The vertical scrollbar should shrink or grow vertically when the control is resized, so 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 scrollbar. You indicate that the scrollbar is the last control in the row by setting the gridwidth member to REMAINDER.

c.gridwidth  = GridBagConstraints.REMAINDER ;

Complete the vertical scrollbar 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 scrollbar. Because this scrollbar 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 scrollbar from filling the entire width of the control. You want to guarantee that the horizontal scrollbar remains the same width as the ImageCanvas object. Fortunately, the GridBagConstraint class provides a way to tie 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 scrollbar in terms of grid cells. Set this member to 1 so that the horizontal scrollbar 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 ;

Then add the horizontal scrollbar. First associate it with the constraints object and then add it to the layout:

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

Finally, declare ScrollingPictureWindow to be the AdjustmentListener object for both scrollbars:

        vertBar.addAdjustmentListener( this ) ;
        horzBar.addAdjustmentListener( this ) ;

Depending on where your control is used, it may be resizeable. You handle resizing by over-riding the Component.setBounds() method. This method is called every time a control is resized. The first thing your function does is to call the superclass's setBounds() 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 setBounds( int x,
                                    int y,
                                    int width,
                                    int height) {
        super.setBounds( x, y, width, height ) ;

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

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

            horzBar.setEnabled( false ) ;

If the control width is not greater than the horizontal scrollbar, enable the horizontal scrollbar:

        } else {
            horzBar.setEnabled( true ) ; 

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

            Rectangle bndRect = getBounds() ;
            int barWidth = vertBar.getPreferredSize().width ; 


NOTE: When working with scrollbars, 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 scrollbar. You always set the minimum value of the scrollbar to zero. 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);
            block = 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 zero, you make the position zero:

           int oldMax = horzBar.getMaximum() ;
            if ( oldMax == 0) {
                imgX = 0 ;

If the old maximum value is not zero, 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 scrollbar parameters:

horzBar.setValues( imgX, block, 0, max ) ;
            horzBar.setBlockIncrement( block ) ;

        }

Use the same algorithm to set the vertical scrollbar.

Add the Class as the Listener

The scrolling picture-window control is especially concerned with scrollbar events. All other types of events are passed on and handled outside your program.

You start by implementing the AdjustmentListener interface and overriding the adjustmentValueChanged() method. Because the ScrollingPictureWindow class is designated as the listener for both scrollbars, it gets all the scrollbar events. When the user adjusts the scrollbars, you reset the imgX and imgY variables and call repaint():

public void adjustmentValueChanged(AdjustmentEvent e) {
        imgY = vertBar.getValue() ;
        imgX = horzBar.getValue() ;
        imageCanvas.repaint();

    }

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. You use this class by combining it with the testPictureWindow applet class from Listing 22.1, earlier in this chapter. The ScrollingPictureWindow class appears in Listing 22.2 and can also be found on the accompanying CD-ROM.

Listing 22.2. The ScrollingPictureWindow class.

class ScrollingPictureWindow 
                    extends Panel 
                    implements AdjustmentListener{

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

    int imgWidth ;
    int imgHeight ;
    int imgX ;
    int imgY ;
    int block ;

    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 ) ;
        }

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

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

        vertBar.addAdjustmentListener( this ) ;
        horzBar.addAdjustmentListener( this ) ;
    }

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

        super.setBounds( x, y, width, height ) ;
        if ( width > imgWidth + vertBar.getBounds().width ) {
            horzBar.setEnabled( false ) ;
        } else {
            horzBar.setEnabled( true ) ;
            Rectangle bndRect = getBounds() ;
            int barWidth = vertBar.getPreferredSize().width ;
            int max = imgWidth - (bndRect.width - barWidth);
            block = max/10 ;
            int oldMax = horzBar.getMaximum() ;
            if ( oldMax == 0) {
                imgX = 0 ;
            } else {
            imgX = (int)(((float)imgX/(float)oldMax) *
                                           (float)max) ;
            }
            horzBar.setValues( imgX, block, 0, max ) ;
            horzBar.setBlockIncrement( block ) ;
        }

        if (height > imgHeight + horzBar.getBounds().height) {
            vertBar.setEnabled( false ) ;
        } else {
            vertBar.setEnabled( true ) ;
            Rectangle bndRect = getBounds() ;
            int barHeight = horzBar.getPreferredSize().height ;
            int max = imgHeight - (bndRect.height - barHeight) ;
            block = max/10 ;
            int oldMax = vertBar.getMaximum() ;
            if ( oldMax == 0) {
                imgY = 0 ;
            } else {
            imgY = (int)(((float)imgY/(float)oldMax) *
                                           (float)max) ;
            }
            vertBar.setValues( imgY, block, 0, max ) ;
            vertBar.setBlockIncrement( block ) ;
        }
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
        imgY = vertBar.getValue() ;
        imgX = horzBar.getValue() ;
        imageCanvas.repaint();
    }
};

class ImageCanvas extends Canvas {
    Image canvasImg ;
    public ImageCanvas( Image img ) {
        canvasImg = img ;
    }

    public void paint(Graphics g) {
        if ( getParent().isEnabled() ) {
            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 22.3 and 22.4 show the testPassWindow applet.

Figure 22.3.

The testPassWindow applet before entering the password.

Figure 22.4.

The testPassWindow applet after entering the password.

This applet 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 in the same way you create controls to embed in your applets.

The testPassWindow applet begins by creating passField, Button, and ScrollingPictureWindow objects:

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

public class testPassWindow 
                     extends Applet 
                     implements ActionListener {
    passField              passwordField ;
    Button                 verifyButton ;

    ScrollingPictureWindow pictureWindow ;

The init() method creates the member objects and places them in the control using the GridBagLayout layout manager (see Chapter 14, "The Windowing (AWT) Package," for details on the GridBagLayout layout manager and the associated GridBagConstraints classes). Notice that the passField and ScrollingPictureWindow controls are used as if they were native AWT controls. Finally, init() registers testPassWindow as the listener for the Verify button:

    public void init() {
        GridBagLayout gridbag = new GridBagLayout();
        setLayout( gridbag ) ;
        {
        GridBagConstraints c = new GridBagConstraints();
        passwordField = new passField( 30 ) ;
        c.fill       = GridBagConstraints.BOTH ;
        c.gridx      = 1 ;
        c.gridy      = 1 ;
        c.weightx    = 1.0;
        gridbag.setConstraints(passwordField, c);
        add( passwordField ) ;
        }

        {
        GridBagConstraints c = new GridBagConstraints();
        verifyButton  = new Button( "Verify" ) ;
        c.gridx      = 2 ;
        c.gridy      = 1 ;
        c.gridwidth  = GridBagConstraints.REMAINDER ;
        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.weightx    = 1.0 ;
        c.weighty    = 1.0 ;
        gridbag.setConstraints(pictureWindow, c);
        add( pictureWindow ) ;
        }

        pictureWindow.setEnabled( false ) ;
        verifyButton.addActionListener( this ) ;

    }

To tie all these controls together, the testPassWindow class implements the ActionListener interface. This combination of the password control and the picture-window control is a good candidate for encapsulation in a Panel so that it would become one big composite control.

The testPassWindow class implements actionPerformed() from the ActionListener interface. This function simply checks whether the password entered by the user is valid and then enables and paints the image:

    public void actionPerformed(ActionEvent e) {
        if ( passwordField.getString().equals( "secret" ) ) {
            pictureWindow.setEnabled( true ) ;
            pictureWindow.imageCanvas.invalidate() ;
            pictureWindow.imageCanvas.repaint() ;
            verifyButton.setEnabled( false ) ;
            passwordField.setEnabled( false ) ;
        } 

};

Summary

Sometimes, your applets or applications require UI functionality beyond what is provided by the AWT. You can use subclassing and 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 the associated AWT control.

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 is a subclass of Panel and serves to encapsulate the Canvas control and two scrollbars.

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.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.