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.
|