TOC
BACK
FORWARD
HOME

Java 1.1 Unleashed

- 41 -
A Fancy Button Bean

by Michael Morrison

IN THIS CHAPTER

  • Designing the Fancy Button Bean
  • Developing the Fancy Button Bean
  • Testing the Fancy Button Bean
  • Enhancing the Fancy Button Bean

In this chapter, you learn to develop a bean that is somewhat similar in function to the standard AWT button provided in Java 1.1, but a little fancier. You develop this bean entirely from scratch--while observing the design suggestions covered in the previous chapter. The bean you create in this chapter serves as a great starting point for developing other beans because it tackles many of the common issues related to building practical beans. By the end of the chapter, you will have both the know-how and the source code necessary to start building beans of your own.

Along with developing the code for a custom bean in this chapter, you learn how to test the bean in the BeanBox test container, which is a vital part of the development process. You'll find that the BeanBox is a fun way to try out your beans as you develop them. You finish the chapter with a brainstorming session for some ways in which you can improve and extend the bean on your own.

Designing the Fancy Button Bean

As you learned in the previous chapter, the first step in building any bean is to come up with a preliminary design. This design should be carried out to some degree of detail before embarking on the actual coding of the bean. The bean you develop in this chapter is a fancy button bean, meaning that it functions similarly to the standard Java AWT button but adds some functionality to make it a little fancier. So that you can see how the design for the fancy button bean comes together, it doesn't derive from the AWT Button class but instead implements all its own button functionality from scratch.

A few things about the fancy button bean set it apart from the standard AWT button. Like the AWT button, the fancy button bean enables you to set the label for the button to any text you choose. However, it also enables you to set the font for the label, which can be one of a variety of typefaces in virtually any size. The fancy button bean knows how to automatically resize itself to accommodate the varying size of the label based on the font selection. Another neat feature of the fancy button bean is that you can change the background and foreground colors. The background color corresponds to the face of the button, and the foreground color determines the color of the label text.

The fancy button bean also supports a feature in which it can be used to represent an on/off state, much like a checkbox. In this mode--which we'll call sticky mode--instead of automatically raising after you click it, the button remains down. Another mouse click restores it to its normal position. This feature is useful in toolbars, where some buttons reflect a two-state option. A good example is the bold button in some popular word processor toolbars, which remains down when clicked to indicate that the bold option is turned on.

Now that you have an idea of what the fancy button does, let's move on to designing some specifics about it. Recall from the previous chapter that the design of a bean can be divided into three major parts: properties, methods, and events. The following sections focus on the fancy button bean and the design of these major functional parts.

Properties

The properties for the fancy button bean must somehow represent the information required to support the functionality you just learned about. Fortunately, you don't have to specifically define all these properties yourself because you are going to derive the bean from an existing AWT class, which provides a great deal of overhead and some very useful properties. The AWT class I'm referring to is Canvas, which represents a rectangular area onto which you can draw or trap input events from the user. The Canvas class provides the base functionality typically required of graphical beans, including background color, foreground color, name, and font properties. Actually, most of this functionality is provided by the Component class, which is the parent of Canvas.

Even though the Canvas and Component parent classes help out, you still must provide some properties of your own. You know that the fancy button bean will have a label, so you can start off with a string label property. You also know that the bean must support a sticky mode, which can easily be represented by a boolean property. Because the bean supports a sticky mode, you also need a property to keep track of whether the bean is up or down. Another boolean property serves this purpose just fine. These three properties are pretty well sufficient for modeling the high-level functionality required of the bean. In fact, combined with the properties provided by the Canvas class, these properties form the full set of properties for the fancy button bean. Keep in mind, however, that although these are the only three properties you must define yourself, there may be other member variables in the fancy button bean that aren't exposed as properties. You learn about these member variables when you start writing the actual code in the "Developing the Fancy Button Bean" section of this chapter.

Following are the properties required of the fancy button bean:

  • Label

  • Sticky mode (on/off)

  • Button state (up/down)

Methods

Now that you've defined the properties, it's time to move on to determine the public methods required of the fancy button bean. The easiest place to start is with the accessor methods for the bean. You already know the properties for the bean, so determining the accessor methods is pretty easy. Both the label and sticky mode properties for the bean are readable and writeable, so you need a pair of accessor methods for each of them. The property representing the bean's up/down state is a little different because the state is determined by the user clicking the button. In other words, this property shouldn't be directly writeable like the other two properties; instead, it should always be set indirectly by interacting with the button visually. So, for this property, you need only one accessor method: a getter method.

Recall that the fancy button bean is supposed to resize itself based on the typeface and size of the font for the label text. This is necessary because selecting a larger font requires more physical space on the bean's surface to display the label text. Knowing that the bean must resize itself whenever the font property's value changes, what do you think this means in terms of accessor methods? If you're thinking you must override the setter method for the font property, you are exactly right. It turns out that the setter method for the font property is defined in the Component class, along with the font property itself. By overriding it in the fancy button bean, you can easily resize the bean after setting the new font value.

Beyond accessor methods, you have to consider the other public methods the bean must provide. The fancy button bean needs only two other public methods. The first of these, paint(), is overridden from the parent Canvas class to enable the bean to paint itself. The paint() method is pretty much required of all graphical beans because it provides a central location for placing all of a bean's drawing code. The other public method required of the bean isn't quite as obvious: It is the getPreferredSize() method, which calculates and returns the preferred size of the bean based on its current state. This method is necessary only because the fancy button bean has a preferred size based on the label text and font.

Following are the public methods required of the fancy button bean:

  • Label property getter/setter methods

  • Sticky mode property getter/setter methods

  • Button state property getter method

  • Overridden font property setter method

  • Overridden paint() method

  • Overridden getPreferredSize() method

Events

The last part of the fancy button bean you must focus on at this stage is events. Because the bean is a button, it has to fire an action event when the user clicks it. This is what enables the bean to be wired to other beans or applications in order to trigger them with a button click. Because the event fired in this case is an action event, part of supporting it is providing event listener registration methods. A pair of these methods is all you need to support the addition and removal of action event listeners.

The actual firing of the action event is another issue altogether. The bean is required to support some type of mechanism by which an action event is passed on to all the listeners registered. This mechanism must be handled by a single event-processing method that iterates through the listeners and dispatches the event to each one appropriately.

The final area of interest relating to events is how the bean processes events for itself. It's one thing to be able to broadcast events to others, but the fancy button bean must also manage events that occur within itself. For example, when the user clicks the bean, the bean must change its appearance to show the button pressing down. The events the bean must respond to are focus changes, mouse clicks, mouse drags, and key presses. The focus-change events must be processed because the button draws a focus rectangle on its surface whenever it has focus. The mouse click-and-drag events must be processed so that the bean can change its state appropriately and act like a button. Finally, the key-press events must be processed to provide a keyboard interface for the bean (so that it can simulate a mouse click for keys like the Enter key). These events are processed in individual event-processor methods that are overridden from the bean's parent class.

Following are the event-related methods required of the fancy button bean:

  • Action event listener registration methods

  • Action event-processing method for firing events

  • Focus event-processing method

  • Mouse click event-processing method

  • Mouse drag event-processing method

  • Key press event-processing method

Developing the Fancy Button Bean

Now that you have a pretty solid design, you're ready to jump into the code for the fancy button bean. You will probably be pleasantly surprised by how little difference there is between the code for beans and the code for normal Java classes. Beans are, in fact, normal Java classes with support for a few extra facilities such as introspection and serialization. In many cases, these extra facilities are handled automatically, so the bean code really does look just like a normal Java class.

In the following sections, you learn about different parts of the code for the fancy button bean. Because the discussion tackles small parts of the code, you may not immediately see how everything fits into the overall bean class structure purely from a code perspective. If you are having trouble with this approach, check out the complete source code for the bean, which is included on the accompanying CD-ROM. The main code for the fancy button bean is in the FancyButton class, which appears on the CD-ROM in the file FancyButton.java.

Properties and Member Variables

The first place to start in developing the fancy button bean is laying out the properties. Following are the member variable properties for the bean based on the initial design:

private String    label;
private boolean   sticky;
private boolean   down;

As you can see, these member variables directly correspond to the properties mentioned in the initial design. Notice that they are all built-in Java types, which means that property editors are already provided by JavaBeans for each of them: You don't have to do any extra work to provide visual editing support for the fancy button bean's properties. Along with these properties, the bean also requires a few other member variables for internal use:

private transient ActionListener  actionListener = null;
private transient boolean         hasFocus;

The actionListener member is an ActionListener interface used to manage the list of action event listeners. This list is used by the action event-processing method to fire events to each listener. You learn more about the actionListener member later in the chapter when you get into the specifics of how the fancy button bean processes events. The hasFocus member is used to keep track of the focus state of the bean. The focus is important to keep up with because it affects the visual appearance of the bean.

You may be a little curious about why both of these member variables are declared as transient. Transient member variables are those that aren't saved and restored through bean persistence. The actionListener and hasFocus member variables represent information specific to a particular session, meaning that it isn't necessary to save them persistently as part of the bean's state. That sums up all the member variables for the bean. Let's move on to the constructors!

Constructors

The fancy button bean provides two constructors: a default constructor and a detailed constructor that takes all three properties as arguments. Following is the code for both of these constructors:

public FancyButton() {
  this("Push Me", false, false);
}
public FancyButton(String l, boolean s, boolean d) {
  // Allow the superclass constructor to do its thing
  super();
  // Set properties
  label = l;
  sticky = s;
  down = d;
  setFont(new Font("Dialog", Font.PLAIN, 12));
  setBackground(Color.lightGray);
  // Enable event processing
  enableEvents(AWTEvent.FOCUS_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK |
    AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);

}

The first constructor simply calls the second constructor with default property values. The second constructor is responsible for actually doing all the work. The second constructor begins by calling the superclass constructor and then moves on to set all the properties. Notice that the two inherited properties, font and background color, are set by calling their setter methods instead of directly setting the member variables. We do this because properties are always declared as private, which means that you are required to use a setter method to access them. In this case, it works out well because the fancy button bean provides its own setter method for the font property.

With the properties set, the constructor moves on to enabling a group of events for the bean to process by calling the enableEvents() method. If it didn't call this method, the bean would be incapable of directly trapping any events. The purpose of selectively enabling certain events is to optimize the event-handling procedure by looking for only the events in which you are specifically interested. In this case, the fancy button bean is interested in focus, mouse, mouse move, and key events.

Accessor Methods

Moving right along, the next piece of the bean puzzle is the accessor methods, which provide access to bean properties. Following is the code for the label property's accessor methods:

public String getLabel() {
  return label;
}
public void setLabel(String l) {
  label = l;
  sizeToFit();

}

The getLabel() getter method is pretty self-explanatory in that it simply returns the value of the label member variable. The setLabel() setter method is slightly more involved in that it calls the sizeToFit() method after setting the label member variable. The sizeToFit() method, which you learn about in detail in the "Support Methods" section, later in this chapter, is responsible for determining the optimal button size based on the label and font.

The accessor methods for the sticky property, isSticky() and setSticky(), are very minimal in that they simply get and set the value for the sticky member variable, with no additional functionality. Likewise, the getter method for the button state, isDown(), simply returns the value of the down member variable. Things get a little more interesting when you get to the overridden setter method for the font property, setFont(), which follows:

public void setFont(Font f) {
  super.setFont(f);
  sizeToFit();

}

The whole point of overriding this method is to make sure that the bean is resized when any change occurs in the value of the font property. Notice that the superclass's setFont() method is called to perform the actual setting of the property before the bean is resized with a call to sizeToFit().

Public Methods

There are two important public methods in the bean to look at now: paint() and getPreferredSize().The paint() method is used to paint the visual appearance of the bean; its source code follows:

public synchronized void paint(Graphics g) {
  int width = getSize().width;
  int height = getSize().height;
  // Paint the background with 3D effects
  g.setColor(getBackground());
  g.fillRect(1, 1, width - 2, height - 2);
  g.draw3DRect(0, 0, width - 1, height - 1, !down);
  g.setColor(Color.darkGray);
  if (down) {
    g.drawLine(1, height - 3, 1, 1);
    g.drawLine(1, 1, width - 3, 1);
  }
  else {
    g.drawLine(2, height - 2, width - 2, height - 2);
    g.drawLine(width - 2, height - 2, width - 2, 1);
  }
  // Paint the foreground text
  g.setColor(getForeground());
  g.setFont(getFont());
  FontMetrics fm = g.getFontMetrics();
  if (down)
    g.drawString(label, ((width - fm.stringWidth(label)) / 2) + 2,
      ((height + fm.getMaxAscent() - fm.getMaxDescent()) / 2) + 1);
  else
    g.drawString(label, (width - fm.stringWidth(label)) / 2,
      (height + fm.getMaxAscent() - fm.getMaxDescent()) / 2);
  // Paint the focus rect
  if (hasFocus) {
    g.setColor(Color.gray);
    g.drawRect(4, 4, width - 8, height - 8);
  }

}

The paint() method starts off by getting the width and height of the bean, filling the background, and painting a 3D effect around the edges. Notice that the 3D effect is painted differently depending on the value of the down member variable. The foreground label text is painted next by selecting the appropriate font and calculating the coordinates so that the text is centered within the bean. Finally, if the hasFocus member variable is set to true, a focus rectangle is drawn on the bean to indicate that it has the input focus.

The getPreferredSize() method is used to return the favored size of the bean to any interested party, including itself. When I say "favored size," I mean the ideal visual size of the bean so that the label text is positioned in the bean with a proper spacing from the edges. The calculation of this size takes into account the selected font and the label text. Following is the code for the getPreferredSize() method:

public Dimension getPreferredSize() {
  // Calculate the preferred size based on the label text
  FontMetrics fm = getFontMetrics(getFont());
  return new Dimension(fm.stringWidth(label) + TEXT_XPAD,
    fm.getMaxAscent() + fm.getMaxDescent() + TEXT_YPAD);

}

Event-Registration Methods

The event-registration methods in the fancy button bean are used to enable the addition and removal of listeners for the bean's action events. The code for these methods follows:

public synchronized void addActionListener(ActionListener l) {
  actionListener = AWTEventMulticaster.add(actionListener, l);
}
public synchronized void removeActionListener(ActionListener l) {
  actionListener = AWTEventMulticaster.remove(actionListener, l);

}

These methods simply add and remove action listeners from the actionListener vector, which is the ActionListener interface used to keep up with what receives action events. The listeners themselves are managed by the underlying AWTEventMulticaster class, which is a helper class in the Java API designed to keep track of event listeners.

Event-Processing Methods

The fancy button bean provides a variety of event-processing methods, primarily because the bean must listen for a few different types of input events to function properly. The first of these methods, processActionEvent(), isn't related to processing an input event, however; instead, it is used to dispatch action events to all the registered action event listeners. Following is the code for this method:

protected void processActionEvent(ActionEvent e) {
  // Deliver the event to all registered action event listeners
  if (actionListener != null)
    actionListener.actionPerformed(e);

}

The processActionEvent() method is responsible for firing actionPerformed events to all registered event listeners. It does this by calling the actionPerformed() method on the actionListener member, which represents a chain of event listeners. The actionPerformed() method is the only method defined in the ActionListener interface and is typically called any time an action occurs in an event source such as a bean. In the case of the fancy button bean, an action is defined as a button push, but an action can mean other things in other beans.

The fancy button bean uses four other event-processing methods, although they act a little differently than processActionEvent(). Following is the code for the first of these methods, processFocusEvent():

protected void processFocusEvent(FocusEvent e) {
  // Get the new focus state and repaint
  switch(e.getID()) {
    case FocusEvent.FOCUS_GAINED:
      hasFocus = true;
      repaint();
      break;
    case FocusEvent.FOCUS_LOST:
      hasFocus = false;
      repaint();
      break;
  }
  // Let the superclass continue delivery
  super.processFocusEvent(e);

}

This method is called whenever the focus for the bean changes. Its only function is to monitor changes in the bean's focus and to update the appearance of the bean accordingly. The processMouseEvent() method is a little more interesting in that it responds to mouse button presses and releases. Following is the code for this method:

protected void processMouseEvent(MouseEvent e) {
  // Track mouse presses/releases
  switch(e.getID()) {
    case MouseEvent.MOUSE_PRESSED:
      down = !down;
      repaint();
      break;
    case MouseEvent.MOUSE_RELEASED:
      if (down && !sticky) {
        fireActionEvent();
        down = false;
        repaint();
      }
      break;
  }
  // Let the superclass continue delivery
  super.processMouseEvent(e);

}

The processMouseEvent() method is responsible for trapping mouse button presses and releases and making sure that the bean behaves appropriately. When the mouse button is pressed, the down member variable is toggled and the bean is repainted. However, when the mouse button is released, the down and sticky member variables are first checked to determine the state of the button. If the bean isn't in sticky mode and is down, an action event is fired by the fireActionEvent() method, which you learn about in the "Support Methods" section. The down member variable is then set to false and the bean is repainted.

The processMouseMotionEvent() method is used to respond to events related to the movement of the mouse. Following is the code for this method:

protected void processMouseMotionEvent(MouseEvent e) {
  // Track mouse drags
  if (e.getID() == MouseEvent.MOUSE_DRAGGED && !sticky) {
    Point pt = e.getPoint();
    if ((pt.x < 0) || (pt.x > getSize().width) ||
      (pt.y < 0) || (pt.y > getSize().height)) {
      if (down) {
        down = false;
        repaint();
      }
    }
    else if (!down) {
      down = true;
      repaint();
    }
  }
  // Let the superclass continue delivery
  super.processMouseMotionEvent(e);

}

The processMouseMotionEvent() method is responsible for detecting mouse drags and making sure that the bean behaves properly. The only purpose of responding to mouse drags is so that the bean button raises up when the mouse is dragged off it (unless it's in sticky mode; in sticky mode, drags have no meaning because the bean changes state as soon as the mouse button is pressed). If the bean isn't in sticky mode, the coordinates of the mouse are checked by the processMouseMotionEvent() method to see whether they fall within the bean's bounding rectangle. If not, processMouseMotionEvent() restores the bean button to its raised position by setting the down member to false. If the mouse is within the bounding rectangle for the bean and the bean is raised, the down member is set to true.

The last of the event-processing methods is the processKeyEventMethod(), which is used to respond to key presses. Following is the code for this method:

protected void processKeyEvent(KeyEvent e) {
  // Simulate a mouse click for certain keys
  if (e.getKeyCode() == KeyEvent.VK_ENTER ||
    e.getKeyChar() == KeyEvent.VK_SPACE) {
    if (sticky) {
      down = !down;
      repaint();
    }
    else {
      down = true;
      repaint();
      fireActionEvent();
      down = false;
      repaint();
    }
  }
  // Let the superclass continue delivery
  super.processKeyEvent(e);

}

The processKeyEventMethod() is used to provide a keyboard interface for the bean. If the bean has focus and the Enter key or spacebar is pressed, the action is treated just like clicking the mouse on the bean. The mouse click is simulated by setting the down member to true, firing an action event by calling fireActionEvent(), and then setting the down member to false. This is kind of a tricky way to get extra functionality without doing much work--something I'm always in favor of!


NOTE: You may be a little confused at this point about the processXXXEvent() methods; one of them is used to dispatch events and the others are used to respond to events. The reason for this apparent inconsistency is that the event-processor methods that respond to events are overridden versions of superclass methods. The original superclass versions are responsible for dispatching the events to listeners; your versions are free to just add response code.

Notice that all four of these methods call their respective superclass versions, which is a sure sign that the superclass methods are doing some additional work. In the case of the processActionEvent() method, there is no superclass version, so it must take on the responsibility of dispatching events to registered listeners.

Support Methods

The fancy button bean uses two private support methods you haven't learned about yet: sizeToFit() and fireActionEvent(). These two methods provide functionality that the bean needs only internally, which is why they are declared as private. Nevertheless, they play a vital role in the inner workings of the bean. The code for the sizeToFit() method follows:

private void sizeToFit() {
  // Resize to the preferred size
  Dimension d = getPreferredSize();
  setSize(d.width, d.height);
  Component p = getParent();
  if (p != null) {
    p.invalidate();
    p.doLayout();
  }

}

The sizeToFit() method is responsible for sizing the bean to fit the label text with just enough space between the text and the border so that the button looks visually appealing. The getPreferredSize() method is used to get the optimal button size, which is then used to resize the bean. Note that calling the setSize() method alone isn't sufficient to resize the bean; you must also notify the bean's parent to lay out the bean again by calling the invalidate() and doLayout() methods on the parent.

The fireActionEvent() method is also important to the internal functioning of the bean. Following is its code:

private void fireActionEvent() {
  processActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
    null));

}

The fireActionEvent() method is simple in that it consists of only a single call to the processActionEvent() method. The purpose of providing the fireActionEvent() method is to clean up the task of firing an action event by hiding the creation of the ActionEvent object passed into processActionEvent(). This isn't a big deal, but it does make the code calling fireActionEvent() in other methods a little cleaner.

Additional Overhead

You've now covered all the source code for the fancy button bean itself. Although the FancyButton class is all you really need to have a fully functioning bean, there is actually one other class worth mentioning: FancyButtonBeanInfo. It is possible to provide explicit information about a bean in an associated bean information class. Part of this explicit information is the graphical icons used to display a bean for selection purposes in application builder tools. It is easy to provide selection icons for a bean through a bean information class. Following is the complete source code for the FancyButtonBeanInfo class, which is a bean information class that defines selection icons for the fancy button bean:

// FancyButtonBeanInfo Class
// FancyButtonBeanInfo.java
package JUL.Source.Chap41.FancyButton;
// Imports
import java.beans.*;
public class FancyButtonBeanInfo extends SimpleBeanInfo {
  // Get the appropriate icon
  public java.awt.Image getIcon(int iconKind) {
    if (iconKind == BeanInfo.ICON_COLOR_16x16) {
      java.awt.Image img = loadImage("FancyButtonIcon16.gif");
      return img;
    }
    if (iconKind == BeanInfo.ICON_COLOR_32x32) {
      java.awt.Image img = loadImage("FancyButtonIcon32.gif");
      return img;
    }
    return null;
  }

}

The only method defined in the FancyButtonBeanInfo class is getIcon(), which is typically called by application builder tools to retrieve an icon representing the bean. Notice that two icons are actually provided by the bean information class using different resolutions. The first icon is 16x16 pixels in size; the second is 32x32 pixels. These two icons give application builder tools some degree of flexibility in representing beans graphically for selection. The BeanBox tool that comes with the BDK uses the 16x16 size icons. Both of the bean icons are provided as GIF 89A images, which is pretty standard for Java. Figure 41.1 shows how the icons look for the fancy button bean.

Figure 41.1.

The bean information icons for the fancy button bean.

One final part of the fancy button bean is required before the bean can be used in an application builder tool. I'm referring to the manifest file required by the JAR file in which the bean is placed. As you learned in Chapter 40, "Creating Your Own Beans," beans must be distributed in a JAR file along with an appropriate manifest file describing the contents of the JAR file.

I'm not going to go into the details of manifest files because you really need to know very little about them in most scenarios. In the simple case of packaging up a bean for distribution, all you have to do is provide a few pieces of information. Signing a file for security purposes is a little more involved, but you don't need to worry about that now. Here is the code for the fancy button bean's manifest file, FancyButton.mf:

Manifest-Version: 1.0
Name: JUL/Source/Chap41/FancyButton/FancyButton.class
Java-Bean: True

As you can see, the manifest file is very simple; you basically provide the name of your bean class and specify that it is, in fact, a bean. It is important to note that the package of the bean is specified as the bean's path in the manifest file. This is the only additional overhead you have to place in the JAR file so that application builder tools can extract information about your bean.

Of course, before you build the JAR file, you still have to compile all the bean's Java source code files into classes using the JDK compiler (javac). Before doing this, you have to make sure that you have the CLASSPATH environment variable set so that the compiler can find the bean's classes. This is necessary because the fancy button bean is defined as being part of a package (JUL.Source.Chap41.FancyButton). You can basically add the root path above the bean package hierarchy to the listing of paths in CLASSPATH. This directory is the parent directory of the JUL directory. If you installed the source code off the CD-ROM into a directory called Stuff, you end up with a directory structure like this:

\Stuff\JUL\Source\Chap41\FancyButton

In this case, you add the path \Stuff to CLASSPATH so that the compiler can locate support classes for the fancy button bean. After setting CLASSPATH, you should be able to compile the fancy button bean by executing the following two commands:

javac FancyButton.java

javac FancyButtonBeanInfo.java

Beans in a JAR File

With the bean successfully compiled, you are ready to create the JAR file for it. Unfortunately, creating the JAR file for a bean isn't quite as straightforward as you may think. As you know, the fancy button bean is part of a package, which means that the classes and related resource files for the bean are found within a hierarchical directory structure dictated by the package name. This directory structure has implications that affect how the bean is placed in a JAR file because classes are always loaded with respect to a package hierarchy. In other words, classes that reside in a package are always referenced from a directory structure that matches the package name, even when the classes are part of a JAR file. Therefore, the classes and resource files for a bean must preserve their directory structure when they are placed in a JAR file.

The package hierarchy of a bean is enforced in a JAR file by way of the manifest file. As you saw a little earlier in this chapter, the fancy button bean's manifest file includes path information based on the package name of the bean. Although the manifest file handles the problem of enforcing a package hierarchy on a bean in a JAR file, it doesn't address a subtle problem in creating the JAR file in the first place. The problem I'm referring to has to do with the fact that the jar utility specifically looks for a bean class file that matches the package directory structure whenever it is used to create a JAR file for a bean. The problem is made more apparent because the jar utility won't even find a bean class file if the utility is run in the same directory where the file is located. The reason for this is that the jar utility tries to traverse a package directory structure to find the class. The solution is to execute the jar utility from a directory above the package directory structure where the bean files are located. In this case, you run the jar utility from the directory just above the JUL directory.

Even though the jar utility expects the bean class to be located within a directory structure, it doesn't automatically know to look for the class files and resources in this structure. This means that you must specify explicit paths when you execute the jar utility from the directory above the package hierarchy. To build a JAR file that contains the fancy button bean and its resources, execute the following command in the directory above the bean package hierarchy:

jar cfm JUL\Source\Chap41\FancyButton\FancyButton.jar
  JUL\Source\Chap41\FancyButton\FancyButton.mf
  JUL\Source\Chap41\FancyButton\*.class
  JUL\Source\Chap41\FancyButton\*.gif

In this command, notice that the paths to all the files used by the jar utility are explicitly declared. This is necessary because the utility is being executed from the top of the bean package hierarchy. If you're thinking that this seems like a roundabout way of doing things, you're exactly right. However, as of the beta 3 release of the BDK, this was the only way to build JAR files that contain beans that are part of a package. Hopefully, JavaSoft is working out a better approach for the final release of the BDK, which may be available by the time you read this. Check the JavaSoft Web site (www.javasoft.com) for the latest information.

One other small problem must be addressed in regard to the jar command just described. Many operating system shells don't provide a big enough command-line buffer to enter commands that long. Fortunately, there is an easy solution to this problem. I created a simple batch file that can be used to add any bean and its associated resources to a JAR file without concern about the length of the command. Here are the contents of this batch file, which I named beanjar.bat:

@echo off
echo Jar'ing %2...
jar cfm %1\%2.jar %1\%2.mf %1\*.class %1\*.gif %1\*.au
echo Rejar'ing finished.

This batch file takes two arguments: a path and a bean name. The path argument describes the relative path of the bean's class files and resources; the bean name is the name of the main bean class file without the .class extension.


NOTE: The beanjar.bat batch file is intended to be an all-purpose utility for creating JAR files for beans. For this reason, it attempts to add all the classes, images, and sounds for a bean to the JAR file, even if a bean doesn't necessarily use images or sounds. For beans that don't use images or sounds, an error message appears stating that no files could be found matching the *.gif and *.au wildcards. You can just ignore this error message if your bean doesn't rely on image or sound resources.

Following is an example of creating a JAR file that contains the fancy button bean using the beanjar.bat batch file:

beanjar JUL\Source\Chap41\FancyButton FancyButton

Keep in mind that this command must still be executed from the top of the package directory hierarchy for the bean. After you execute this command, you should have a JAR file that contains the fancy button bean that is ready to be distributed and used.


NOTE: Keep in mind that the complete source code and related resources for the fancy button bean are located on the accompanying CD-ROM.

Testing the Fancy Button Bean

The previous section mentioned that most application builder tools, including the BeanBox test container, require beans to be packaged as JAR files. You just created a JAR file that contains the fancy button bean, which you will now test in the BeanBox. To add the bean to the BeanBox, you must first copy the JAR file to the jars directory beneath your BDK installation. If you installed the BDK according to the instructions in the previous chapter, the appropriate directory is \jdk\beans\jars. The reason for copying the JAR file to this directory is that the BeanBox looks in this directory for all the beans to add to its ToolBox.


NOTE: As of this writing, the JavaBeans architects were in the process of working out a better way to integrate new beans into the BeanBox. One of the possible solutions was a new menu command under the Edit menu that would let you add beans using a file-selection dialog box. Check your version of the BeanBox to see whether it supports this functionality.

You launch the BeanBox by executing the run.bat batch file, which you learned about in the previous chapter. The BeanBox should appear with your fancy button bean added to the ToolBox.

Figure 41.2

shows what the ToolBox looks like with the bean added.


NOTE: Keep in mind that the run.bat batch file alters the CLASSPATH environment variable so that the BeanBox can run properly. You have to manually set CLASSPATH back to its original value before compiling any additional beans.

In the figure, notice that the last bean in the ToolBox is the fancy button bean, complete with the 16x16 icon you specified in the bean information class. Add the fancy button bean to the BeanBox by clicking it in the ToolBox and then clicking the main container window. Fig- ure 41.3 shows the newly added fancy button bean.

Figure 41.3.

The BeanBox main container window with the fancy button bean added.

Now that the bean has been added to the container window, the real fun begins. Check out Figure 41.4, which shows the PropertySheet window for the fancy button bean.

Figure 41.4.

The BeanBox PropertySheet window for the fancy button bean.

The PropertySheet window shows all the properties for the bean, including the inherited foreground color, background color, font, and name properties, as well as your own sticky and label properties. All these properties are fully editable using the PropertySheet window. Try editing the label and font for the bean because they impact the appearance the most. Figure 41.5 shows the bean with the label and font properties modified.

Figure 41.5.

The BeanBox main container with a modified fancy button bean.

You should also try out the sticky mode for the bean just to make sure that it works the way you expected it to. Also keep in mind that the bean is designed to fire action events whenever it is clicked, so you can easily wire it to other beans just as you did with the OurButton bean in the previous chapter. Because you already know how to wire buttons to other beans, I'll let you explore that use of the fancy button bean on your own.

Enhancing the Fancy Button Bean

The last topic of this chapter deals with some areas in which the fancy button bean can be improved. I did all the work for you in developing the guts of the bean, so I want to give you some ideas about some modifications you can make yourself. No matter how good a bean is, there is always room for improvement in one way or another, and this bean is no different. Following are a few suggestions for ways to add more features to the bean:

  • Add support for images

  • Add button down/up events for the sticky mode

The first suggestion involves adding support for images, which means that the button would display an image along with the label text. As you are no doubt aware, image buttons are popular in most graphical environments. This addition can be as simple as adding a string property that holds the filename for an image drawn in the paint() method.

The second suggestion is related to the sticky mode of the button. In some situations, it may be useful for the button to fire events based on the state changing while it is in the sticky mode. In other words, it might be nice to know when the button is being pushed and raised. Providing this functionality consists of adding a new event interface and providing event types for the button being pushed and raised in the sticky mode.

You may not feel up to the challenge of implementing these suggestions just yet because this was your first bean. However, at least try to think about the conceptual aspects of modifying the bean to support these features. Doing so will help you a great deal in gaining bean design experience.

Summary

This chapter introduced you to the fine art of JavaBeans programming at a practical level; you built your very first bean! Although the theory you learned in the last few chapters was important in its own right, this chapter finally showed you how to do something real with JavaBeans. You started the chapter with an idea for a fancy button bean, carried it through a preliminary design, and completed its implementation. You then took the bean for a spin and finished off by brainstorming some ways to improve it on your own. How empowering!

One of the most important things you learned in this chapter is how little JavaBeans programming differs from traditional Java programming. This chapter should have proved to you that beans are just Java classes with some extra features thrown in. This is no accident, seeing how the folks at JavaSoft wanted to make it easy for Java programmers to shift to JavaBeans programming. You should have been able to follow along in this chapter without too much trouble because the bean you developed is so similar to normal AWT Java classes.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.