If you find that no AWT component satisfies your needs, you can create your own. This is usually done either by extending an existing component or by starting from scratch. When extending an existing component, you start with the base functionality of an existing object and add to it. The users will not see anything new or different about the object until they start to interact with it, since it is not a new component. For example, a TextField could be subclassed to convert all letters input to uppercase. On the other hand, if you create a new component from scratch, it will appear the same on all platforms (regardless of what the platform's native components look like), and you have to make sure the user can fairly easily figure out how to work with it. Example 5.3 shows how to create your own Component by creating a Label that displays vertically, as opposed to the standard Label Component that displays horizontally. The whole process is fairly easy.
The third possibility for creating your own components involves adding functionality to containers. This is fairly easy to do and can be useful if you are constantly grouping components together. For example, if you are always adding a TextField or Label to go with a Scrollbar to display the value, do it once, and call it something meaningful like LabeledScrollbarPanel. Then whenever you need it again, reuse your LabeledScrollbarPanel. Think about reusability whenever you can.
With Java 1.1, the colors for these new components should be set to color values consistent to the user's platform. This is done through color constants provided in the SystemColor class introduced in Chapter 2, Simple Graphics.
When you create new components, they must meet three requirements:
If initializing the component requires information about display characteristics (for example, you need to know the default Font), you must wait until the object is displayed on the screen before you initialize it. This is done by overriding the addNotify() method. First, call super.addNotify() to create the peer; you can now ask for platform-dependent information and initialize your component accordingly. Remember to override getPreferredSize() and getMinimumSize() (the Java 1.0 names are preferredSize() and minimumSize()) to return the proper dimensions for the new component, so that layout management works properly. There can be other support methods, depending upon the requirements of the object. For example, it is helpful, but not required, to provide a toString() or paramString() method.
Creating a new component sounds a lot harder than it is. Example 5.3 contains the source for a new component called VerticalLabel. It displays a label that reads from top to bottom, instead of from left to right, and can be configured to display its text right or left justified or centered. Figure 5.5 displays the new component VerticalLabel in action.
import java.awt.*; public class VerticalLabel extends Canvas { public static final int LEFT = 0; public static final int CENTER = 1; public static final int RIGHT = 2; private String text; private int vgap; private int alignment; Dimension mySize; int textLength; char chars[]; // constructors public VerticalLabel () { this (null, 0, CENTER); } public VerticalLabel (String text) { this (text, 0, CENTER); } public VerticalLabel (String text, int vgap, int alignment) { this.text = text; this.vgap = vgap; this.alignment = alignment; } void init () { textLength = text.length(); chars = new char[textLength]; text.getChars (0, textLength, chars, 0); Font f = getFont(); FontMetrics fm = getFontMetrics (f); mySize = new Dimension(0,0); mySize.height = (fm.getHeight() * textLength) + (vgap * 2); for (int i=0; i < textLength; i++) { mySize.width = Math.max (mySize.width, fm.charsWidth(chars, i, 1)); } } public int getAlignment () { return alignment; } public void addNotify () { super.addNotify(); init(); // Component must be visible for init to work } public void setText (String text) {this.text = text; init();} public String getText () {return text; } public void setVgap (int vgap) {this.vgap = vgap; init();} public int getVgap () {return vgap; } public Dimension preferredSize () {return mySize; } public Dimension minimumSize () {return mySize; } public void paint (Graphics g) { int x,y; int xPositions[]; int yPositions[]; // Must redo this each time since font/screen area might change // Use actual width for alignment Font f = getFont(); FontMetrics fm = getFontMetrics (f); xPositions = new int[textLength]; for (int i=0; i < textLength; i++) { if (alignment == RIGHT) { xPositions[i] = size().width - fm.charWidth (chars[i]); } else if (alignment == LEFT) { xPositions[i] = 0; } else {// CENTER xPositions[i] = (size().width - fm.charWidth (chars[i])) / 2; } } yPositions = new int[textLength]; for (int i=0; i < textLength; i++) { yPositions[i] = (fm.getHeight() * (i+1)) + vgap; } for (int i = 0; i < textLength; i++) { x = xPositions[i]; y = yPositions[i]; g.drawChars (chars, i, 1, x, y); } } protected String paramString () { String str=",align="; switch (alignment) { case LEFT: str += "left"; break; case CENTER: str += "center"; break; case RIGHT: str += "right"; break; } if (vgap!=0) str+= ",vgap=" + vgap; return super.paramString() + str + ",label=" + text; } }
The following code is a simple applet using the VerticalLabel. It creates five instances of VerticalLabel within a BorderLayout panel, with gaps (see Chapter 7, Layouts for more on BorderLayout). The top and bottom labels are justified to the left and right, respectively, to demonstrate justification.
import java.awt.*; import java.applet.*; public class vlabels extends Applet { public void init () { setLayout (new BorderLayout (10, 10)); setFont (new Font ("TimesRoman", Font.BOLD, 12)); add ("North", new VerticalLabel ("One", 10, VerticalLabel.LEFT)); add ("South", new VerticalLabel ("Two", 10, VerticalLabel.RIGHT)); add ("West", new VerticalLabel ("Three")); add ("East", new VerticalLabel ("Four")); add ("Center", new VerticalLabel ("Five")); resize (preferredSize()); } }
The VerticalLabel in Example 5.3 works in both Java 1.0 and 1.1 but is relatively inefficient. When you create one, the system must create a Canvas and the peer of the Canvas. This work doesn't gain you anything; since this is a new component, it doesn't have to match the native appearance of any other component.
In Java 1.1, there's a way to avoid the overhead if you are creating a component that doesn't have to match a native object. This is called a lightweight component. To create one, you just subclass Component itself. To make a lightweight version of our VerticalLabel, we have to change only one line of code.
// Java 1.1 only public class VerticalLabel extends Component
Everything else remains unchanged.