One of the most frequently requested additions to the AWT is a button that can contain an image, rather than just text. Unfortunately, the 1.1 release of the AWT did not add such a component, but it is not too difficult to implement a decent version ourselves. The idea is to start with the ImageLabel developed in Section 11.14. If you recall, this was simply a Canvas that resized itself to fit around a supplied image. Options let you stretch the image or leave empty margins around it. To make this into a button, you first draw a 3D border in the margin. Next, you monitor mouse events, inverting the 3D border and displaying a grayed-out image when the button is pressed. Finally, when the button is released, you generate action events so that ImageButton and Button event-handling code can be identical.
The following subsections describe the behavior of the ImageButton class; Listing 13.14 gives the source code.
This creates an ImageButton from the specified GIF or JPEG file.
This constructor turns the specified String into a URL, then creates an ImageButton as above.
If you already have an Image, you can use this constructor to place it in a button.
This makes an ImageButton using the default image of the ImageLabel class (Section 11.14).
Listing 13.13 creates four rows of image buttons. The first and third have varying border thicknesses and are sized to exactly fit the image they display. The second and fourth vary the button size in addition to varying the border width. Figures 13-8 and 13-9 show the results on Windows 95 and Solaris.
Listing 13.13
|
|---|
import java.applet.Applet;
import java.awt.*;
public class ImageButtons extends Applet {
public void init() {
addButtons("images/Marty-Plate-Small.gif", 75, 39);
addButtons("images/Java-Logo.gif", 55, 50);
}
private void addButtons(String file,
int width, int height) {
Panel panel = new Panel();
ImageButton button;
for(int i=0; i<3; i++) {
button = new ImageButton(getCodeBase(), file);
button.setBorder(button.getBorder() + 4*i);
panel.add(button);
}
add(panel);
panel = new Panel();
for(int i=0; i<3; i++) {
button = new ImageButton(getCodeBase(), file);
button.setBorder(button.getBorder() + 4*i);
button.resize(width*(i+1), height*(i+1));
panel.add(button);
}
add(panel);
}
}
|
ImageButton inherits a number of methods such as getBorder, setBorder, getBorderColor, and setBorderColor from the ImageLabel class (Section 11.14). It also inherits methods like getBackground, setBackground, and resize from the Component class. If you resize the button, be sure to do so before displaying it. In addition, you can use the following methods:
This returns an int that is combined via logical "and" with the red, green, and blue values of the image to create the grayed out version. The default is 0xffafafaf, where the last three bytes represent the parts that will go with red, green, and blue values, respectively.
This returns the Image that is being used when the button is pressed down. For instance, the middle button of the third row of Figures 13-8 and 13-9 show the gray image (since that button is pressed).
This changes the darkness value described above.
In general, you want to let ImageButton automatically determine the image that should be displayed when the button is pressed, using a grayed-out version of the normal image. However, you can supply it explicitly using this method.
You handle events exactly the same way as with regular buttons in Java 1.0.
Listing 13.14 presents the source code for the ImageButton.
Listing 13.14
|
|---|
import java.awt.*;
import java.net.*;
import java.awt.image.*; // For ImageFilter stuff
//======================================================
/**
* A button class that uses an image instead of a
* textual label. Clicking and releasing the mouse over
* the button triggers an ACTION_EVENT, so you can add
* behavior in the same two ways as you with a normal
* Button (in Java 1.0):
* <OL>
* <LI>Make an ImageButton subclass and put the
* behavior in the action method of that subclass.
* <LI>Use the main ImageButton class but then catch
* the events in the action method of the Container.
* </OL>
* <P>
* Normally, the ImageButton's preferredSize (used,
* for instance, by FlowLayout) is just big enough
* to hold the image. However, if you give an explicit
* resize or reshape call <B>before</B> adding the
* ImageButton to the Container, this size will
* override the defaults.
* <P>
* @author Marty Hall (hall@apl.jhu.edu)
* @see Icon
* @see GrayFilter
* @version 1.0 (1997)
*/
public class ImageButton extends ImageLabel {
//----------------------------------------------------
/** Default width of 3D border around image.
* Currently 4.
* @see ImageLabel#setBorder
* @see ImageLabel#getBorder
*/
protected static final int defaultBorderWidth = 4;
/** Default color of 3D border around image.
* Currently a gray with R/G/B of 160/160/160.
* Light grays look best.
* @see ImageLabel#setBorderColor
* @see ImageLabel#getBorderColor
*/
protected static final Color defaultBorderColor =
new Color(160, 160, 160);
private boolean mouseIsDown = false;
//----------------------------------------------------
// Constructors
/** Create an ImageButton with the default image.
* @see ImageLabel#getDefaultImageString
*/
public ImageButton() {
super();
setBorders();
}
/** Create an ImageButton using the image at URL
* specified by the string.
* @param imageURLString A String specifying the URL
* of the image.
*/
public ImageButton(String imageURLString) {
super(imageURLString);
setBorders();
}
/** Create an ImageButton using the image at URL
* specified.
* @param imageURL The URL of the image.
*/
public ImageButton(URL imageURL) {
super(imageURL);
setBorders();
}
/** Creates an ImageButton using the file in
* the directory specified.
* @param imageDirectory The URL of a directory
* @param imageFile File in the above directory
*/
public ImageButton(URL imageDirectory,
String imageFile) {
super(imageDirectory, imageFile);
setBorders();
}
/** Create an ImageButton using the image specified.
* You would only want to use this if you already
* have an image (e.g. created via createImage).
* @param image The image.
*/
public ImageButton(Image image) {
super(image);
setBorders();
}
//----------------------------------------------------
/** Draws the image with the border around it. If you
* override this in a subclass, call super.paint().
*/
public void paint(Graphics g) {
super.paint(g);
if (grayImage == null)
createGrayImage(g);
drawBorder(true);
}
//----------------------------------------------------
// You only want mouseExit to repaint when mouse
// is down, so you have to set that flag here.
/** When the mouse is clicked, reverse the 3D border
* and draw a dark-gray version of the image.
* The action is not triggered until mouseUp.
*/
public boolean mouseDown(Event event, int x, int y) {
mouseIsDown = true;
Graphics g = getGraphics();
int border = getBorder();
if (hasExplicitSize())
g.drawImage(grayImage, border, border,
getWidth()-2*border,
getHeight()-2*border,
this);
else
g.drawImage(grayImage, border, border, this);
drawBorder(false);
return(true);
}
//----------------------------------------------------
/** If cursor is still inside, trigger the action
* event and redraw the image (non-gray, button
* "out"). Otherwise ignore this.
*/
public boolean mouseUp(Event event, int x, int y) {
mouseIsDown = false;
if (inside(x,y)) {
paint(getGraphics());
event.id = Event.ACTION_EVENT;
event.arg = (Object)getImage();
return(action(event, event.arg));
} else
return(false);
}
//----------------------------------------------------
/** Generated when the button is clicked and released.
* Override this in subclasses to give behavior to
* the button. Alternatively, since the default
* behavior is to pass the ACTION_EVENT along to the
* Container, you can catch events for a bunch of
* buttons there.
* @see Component#action
*/
public boolean action(Event event, Object arg) {
debug("Clicked on button for " +
getImageString() + ".");
return(false);
}
//----------------------------------------------------
/** If you move the mouse off the button while the
* mouse is down, abort and do <B>not</B> trigger
* the action. Ignore this if button was not
* already down.
*/
public boolean mouseExit(Event event, int x, int y) {
if (mouseIsDown)
paint(getGraphics());
return(true);
}
//----------------------------------------------------
/** The darkness value to use for grayed images.
* @see #setDarkness
*/
public int getDarkness() {
return(darkness);
}
/** An int whose bits are combined via "and" ("&")
* with the alpha, red, green, and blue bits of the
* pixels of the image to produce the grayed-out
* image to use when button is depressed.
* Default is 0xffafafaf: af combines with r/g/b
* to darken image.
*/
public void setDarkness(int darkness) {
this.darkness = darkness;
}
// Changing darker is consistent with regular buttons
private int darkness = 0xffafafaf;
//----------------------------------------------------
/** The gray image used when button is down.
* @see #setGrayImage
*/
public Image getGrayImage() {
return(grayImage);
}
/** Sets gray image created automatically from regular
* image via an image filter to use when button is
* depressed. You won't normally use this directly.
*/
public void setGrayImage(Image grayImage) {
this.grayImage = grayImage;
}
private Image grayImage = null;
//----------------------------------------------------
private void drawBorder(boolean isUp) {
Graphics g = getGraphics();
g.setColor(getBorderColor());
int left = 0;
int top = 0;
int width = getWidth();
int height = getHeight();
int border = getBorder();
for(int i=0; i<border; i++) {
g.draw3DRect(left, top, width, height, isUp);
left++;
top++;
width = width - 2;
height = height - 2;
}
}
//----------------------------------------------------
private void setBorders() {
setBorder(defaultBorderWidth);
setBorderColor(defaultBorderColor);
}
//----------------------------------------------------
// The first time the image is drawn, update() is
// called, and the result does not come out correctly.
// So this forces a brief draw on loadup, replaced
// by real, non-gray image.
private void createGrayImage(Graphics g) {
ImageFilter filter = new GrayFilter(darkness);
ImageProducer producer =
new FilteredImageSource(getImage().getSource(),
filter);
grayImage = createImage(producer);
int border = getBorder();
if (hasExplicitSize())
prepareImage(grayImage, getWidth()-2*border,
getHeight()-2*border, this);
else
prepareImage(grayImage, this);
super.paint(g);
}
//----------------------------------------------------
}
//======================================================
/** Builds an image filter that can be used to gray-out
* the image.
* @see ImageButton
*/
class GrayFilter extends RGBImageFilter {
//----------------------------------------------------
private int darkness = 0xffafafaf;
//----------------------------------------------------
public GrayFilter() {
canFilterIndexColorModel = true;
}
public GrayFilter(int darkness) {
this();
this.darkness = darkness;
}
//----------------------------------------------------
public int filterRGB(int x, int y, int rgb) {
return(rgb & darkness);
}
//----------------------------------------------------
}
//======================================================
|
| Continue to Section 13.4 (Checkboxes). | Return to Chapter 13 table of contents. |
|---|