As noted above, the AWT components in Java 1.1 can all
function as beans. When you write a custom GUI component, it is
not difficult to make it function as a bean as well.
Example 10.1
shows the definition of a custom component,
MultiLineLabel, that is also a bean. This component
is like the standard Label component, but it can
display labels that contain more than one line of text.
Much of the code in this example is taken up with the
mechanics of breaking the label into separate lines and
measuring the size of each of those lines. From the
standpoint of custom AWT components, however, the most
important code is in several required methods that make any
component work correctly. First, there is the
paint() method, of course. All components
(including applets) use this method to display themselves on
the screen. Second, the getPreferredSize() and
getMinimumSize() methods return the preferred and
minimum sizes for the component. These methods must be
implemented so that layout managers can correctly lay the
component out. (Note, though, that for compatibility with
Java 1.0, this example defines the deprecated
preferredSize() and minimumSize() methods
instead.)
MultiLineLabel extends Canvas, which is a
blank component intended primarily for subclassing. When a
component is a subclass of Canvas, it is typically
given its own native window in the underlying window system.
In Java 1.1, however, it is possible to define "lightweight"
custom components by extending Component instead of
Canvas. A lightweight component does not have its
own native window in the underlying window system.
What makes this component a bean is that all of its
properties have get and set accessor
methods. Because MultiLineLabel does not respond to
user input in any way, it does not define any events, so
there are no event listener registration methods required.
Although it is not a formal requirement for a bean, most
beanboxes attempt to instantiate a bean by invoking its
no-argument constructor. Thus, a bean should define a
constructor that expects no arguments.
package oreilly.beans.yesno;
import java.awt.*;
import java.util.*;
/**
* A custom component that displays multiple lines of text with specified
* margins and alignment. In Java 1.1, we could extend Component instead
* of Canvas, making this a more efficient "Lightweight component."
*/
public class MultiLineLabel extends Canvas {
// User-specified attributes
protected String label; // The label, not broken into lines
protected int margin_width; // Left and right margins
protected int margin_height; // Top and bottom margins
protected int alignment; // The alignment of the text
public static final int LEFT = 0, CENTER = 1, RIGHT = 2; // alignment values
// Computed state values
protected int num_lines; // The number of lines
protected String[] lines; // The label, broken into lines
protected int[] line_widths; // How wide each line is
protected int max_width; // The width of the widest line
protected int line_height; // Total height of the font
protected int line_ascent; // Font height above baseline
protected boolean measured = false; // Have the lines been measured?
// Here are five versions of the constructor.
public MultiLineLabel(String label, int margin_width,
int margin_height, int alignment) {
this.label = label; // Remember all the properties.
this.margin_width = margin_width;
this.margin_height = margin_height;
this.alignment = alignment;
newLabel(); // Break the label up into lines.
}
public MultiLineLabel(String label, int margin_width, int margin_height) {
this(label, margin_width, margin_height, LEFT);
}
public MultiLineLabel(String label, int alignment) {
this(label, 10, 10, alignment);
}
public MultiLineLabel(String label) { this(label, 10, 10, LEFT); }
public MultiLineLabel() { this(""); }
// Methods to set and query the various attributes of the component.
// Note that some query methods are inherited from the superclass.
public void setLabel(String label) {
this.label = label;
newLabel(); // Break the label into lines.
measured = false; // Note that we need to measure lines.
repaint(); // Request a redraw.
}
public void setFont(Font f) {
super.setFont(f); // Tell our superclass about the new font.
measured = false; // Note that we need to remeasure lines.
repaint(); // Request a redraw.
}
public void setForeground(Color c) {
super.setForeground(c); // Tell our superclass about the new color.
repaint(); // Request a redraw (size is unchanged).
}
public void setAlignment(int a) { alignment = a; repaint(); }
public void setMarginWidth(int mw) { margin_width = mw; repaint(); }
public void setMarginHeight(int mh) { margin_height = mh; repaint(); }
public String getLabel() { return label; }
public int getAlignment() { return alignment; }
public int getMarginWidth() { return margin_width; }
public int getMarginHeight() { return margin_height; }
/**
* This method is called by a layout manager when it wants to
* know how big we'd like to be. In Java 1.1, getPreferredSize() is
* the preferred version of this method. We use this deprecated version
* so that this component can interoperate with 1.0 components.
*/
public Dimension preferredSize() {
if (!measured) measure();
return new Dimension(max_width + 2*margin_width,
num_lines * line_height + 2*margin_height);
}
/**
* This method is called when the layout manager wants to know
* the bare minimum amount of space we need to get by.
* For Java 1.1, we'd use getMinimumSize().
*/
public Dimension minimumSize() { return preferredSize(); }
/**
* This method draws the label (same method that applets use).
* Note that it handles the margins and the alignment, but that
* it doesn't have to worry about the color or font--the superclass
* takes care of setting those in the Graphics object we're passed.
*/
public void paint(Graphics g) {
int x, y;
Dimension size = this.size(); // Use getSize() in Java 1.1.
if (!measured) measure();
y = line_ascent + (size.height - num_lines * line_height)/2;
for(int i = 0; i < num_lines; i++, y += line_height) {
switch(alignment) {
default:
case LEFT: x = margin_width; break;
case CENTER: x = (size.width - line_widths[i])/2; break;
case RIGHT: x = size.width - margin_width - line_widths[i]; break;
}
g.drawString(lines[i], x, y);
}
}
/** This internal method breaks a specified label up into an array of lines.
* It uses the StringTokenizer utility class. */
protected synchronized void newLabel() {
StringTokenizer t = new StringTokenizer(label, "\n");
num_lines = t.countTokens();
lines = new String[num_lines];
line_widths = new int[num_lines];
for(int i = 0; i < num_lines; i++) lines[i] = t.nextToken();
}
/** This internal method figures out how big the font is, and how wide each
* line of the label is, and how wide the widest line is. */
protected synchronized void measure() {
FontMetrics fm = this.getToolkit().getFontMetrics(this.getFont());
line_height = fm.getHeight();
line_ascent = fm.getAscent();
max_width = 0;
for(int i = 0; i < num_lines; i++) {
line_widths[i] = fm.stringWidth(lines[i]);
if (line_widths[i] > max_width) max_width = line_widths[i];
}
measured = true;
}
}
To prepare a bean for use in a beanbox, you must package it
up in a JAR file, along with any other classes or resource
files it requires. (JAR files are "Java archives"; they
were introduced in Chapter 6, Applets.) Because a
single bean can have many auxiliary files, and because a JAR
file can contain multiple beans, the manifest of the JAR
file must define which of the JAR file entries are beans.
You create a JAR file with c option to the
jar command. When you use the m option in conjunction
with c, it tells jar to read a partial
manifest file that you specify. jar uses the
information in your partially-specified manifest file when
creating the complete manifest for the JAR file. To
identify a class file as a bean, you simply add the
following line to the file's manifest entry:
So, to package up the MultiLineLabel class in a JAR
file, you must first create a manifest "stub" file. Create a
file, perhaps named manifest.stub, with the following
contents:
Name: oreilly/beans/MultiLineLabel.class
Java-Bean: True
Note that the forward slashes in the manifest
file should not be changed to backward slashes on Windows
systems. The format of the JAR manifest file requires
forward slashes to separate directories regardless of the
platform.
Having created this partial manifest file, we can now go
ahead and create the JAR file:
% jar cfm MultiLineLabel.jar manifest.stub oreilly/beans/MultiLineLabel.class
(On a Windows system, you do need to replace the forward slashes
with backslashes in this command line.) If this bean
required any auxiliary files, we would specify them on the
end of the jar command line, along with the class file
for the bean.
The procedure for installing a bean depends entirely
upon the beanbox tool that you are using. For the beanbox
tool shipped with the BDK, all you need to do is to copy the
bean's JAR file into the jars/ directory within the
BDK directory. Once you have done this, the bean will
appear on the palette of beans every time you start up the
application. Alternatively, you can also
load a bean's JAR file at runtime by selecting the Load JAR option from
the File menu of beanbox.
|