Customizing with BeanInfo

So far, everything NetBeans has known about our beans has been determined by low-level reflection—that is, by looking at the methods of our classes. The java.Beans.Introspector class gathers information on a bean using reflection, then analyzes and describes a bean to any tool that wants to know about it. The introspection process works only if the class follows the JavaBeans naming conventions for its methods; furthermore, it gives us little control over exactly what properties and events appear in NetBeans menus. For example, we’ve seen that NetBeans by default shows all the stuff we inherit from the base Swing component. We can change that by creating BeanInfo classes for our beans. A BeanInfo class provides the JavaBeans introspector with explicit information about the properties, methods, and events of a bean; we can even use it to customize the text that appears in menus in NetBeans (and in other IDEs).

A BeanInfo class implements the BeanInfo interface. That’s a complicated proposition; in most situations, the introspector’s default behavior is reasonable. Instead of implementing the BeanInfo interface, we extend the SimpleBeanInfo class, which implements all of BeanInfo’s methods. We can override specific methods to provide the information we want; when we don’t override a method, we’ll get the introspector’s default behavior.

In the next few sections, we’ll develop the DialBeanInfo class that provides explicit information about our Dial bean.

Getting Properties Information

We’ll start out by describing the Dial’s properties. To do so, we must implement the getPropertyDescriptors() method. This method simply returns an array of PropertyDescriptor objects—one for each property we want to publicize.

To create a PropertyDescriptor, call its constructor with two arguments: the property’s name and the class. In the following code, we create descriptors for the Dial’s value, minimum, and maximum properties. We next call a few methods of the PropertyDescriptor class to provide additional information about each property. If our methods were bound (generated PropertyChangeEvents when modified), we’d call the setBound() method of their PropertyDescriptors. Our code is prepared to catch an IntrospectionException, which can occur if something goes wrong while creating the property descriptors, such as encountering a nonexistent method:

    //file: DialBeanInfo.java
    package magicbeans;
    import java.beans.*;
      
    public class DialBeanInfo extends SimpleBeanInfo {
      
      public PropertyDescriptor[] getPropertyDescriptors() {
        try {
          PropertyDescriptor value =
            new PropertyDescriptor("value", Dial.class);
          PropertyDescriptor minimum =
            new PropertyDescriptor("minimum", Dial.class);
          PropertyDescriptor maximum =
            new PropertyDescriptor("maximum", Dial.class);

          return new PropertyDescriptor [] { value, minimum, maximum };
        }
        catch (IntrospectionException e) {
          return null;
        }
      }
    }

Perhaps the most useful thing about DialBeanInfo is that by providing explicit information for our properties, we automatically hide other properties that introspection might find. After compiling DialBeanInfo and packaging it with the Dial, you’ll see that its JComponent properties no longer appear in the NetBeans properties editor. (This has been the case all along if you started with the precompiled example JAR.)

A PropertyDescriptor can provide a lot of other information about a property: the names of the accessor methods (if you decide not to use the standard naming convention), information about whether the property is constrained, and a class to use as a property editor (if the standard property editors aren’t sufficient).

Getting events information

The Dial bean defines its own event: the DialEvent. We’d like to tell development tools about this event so that we can build applications using it. The process for telling the world about our event is similar to what we did previously: we add a method to the DialBeanInfo class called getEventSetDescriptors(), which returns an array of EventSetDescriptors.

Events are described in terms of their listener interfaces, not in terms of the event classes themselves, so our getEventSetDescriptors() method creates a descriptor for the DialListener interface. Here’s the code to add to the DialBeanInfo class:

    public EventSetDescriptor[] getEventSetDescriptors() {
      try {
        EventSetDescriptor dial = new EventSetDescriptor(
          Dial.class, "dialAdjusted",
          DialListener.class, "dialAdjusted");
        dial.setDisplayName("Dial Adjusted");

        return new EventSetDescriptor [] { dial };
      }
      catch (IntrospectionException e) {
        return null;
      }
    }

In this method, we create an EventSetDescriptor object: dial. The constructor for an EventSetDescriptor takes four arguments: the class that generates the event, the name of the event (the name that is displayed, by default, by a development tool), the listener class, and the name of the method to which the event can be delivered. (Other constructors let you deal with listener interfaces that have several methods.) After creating the descriptor, we call the setDisplayName() method to provide a friendly name to be displayed by development tools such as NetBeans. (This overrides the default name specified in the constructor.)

Just as the property descriptors we supply hide the properties that were discovered by reflection, the EventSetDescriptors can hide the other events that are inherited from the base component classes. In theory, when you recompile DialBeanInfo, package it in a JAR, and load it into NetBeans, you should see only the two events that we have explicitly described: our own DialEvent and PropertyChangeEvent (displayed as “Dial Adjusted” and “Bound property change”). Unfortunately, the current version of NetBeans ignores this information.

Once we have an EventSetDescriptor, we can provide other kinds of information about the event. For example, we can state that the event is unicast, which means that it can have only one listener.

Supplying icons

Some of the beans that come with NetBeans are displayed on the palette with a cute icon. This makes life more pleasant for everyone. To supply an icon for the BeanInfo object we have been developing, we have it implement the getIcon() method. You can supply up to four icons, with sizes of 16 × 16 or 32 × 32, in color or monochrome. Here’s the getIcon() method for DialBeanInfo:

    public class DialBeanInfo extends SimpleBeanInfo {
      ...
      public java.awt.Image getIcon(int iconKind) {
      
        if (iconKind == BeanInfo.ICON_COLOR_16x16) {
          return loadImage("DialIconColor16.gif");
        } else
        if (iconKind == BeanInfo.ICON_COLOR_32x32) {
          return loadImage("DialIconColor32.gif");
        } else
        if (iconKind == BeanInfo.ICON_MONO_16x16) {
          return loadImage("DialIconMono16.gif");
        } else
        if (iconKind == BeanInfo.ICON_MONO_32x32) {
          return loadImage("DialIconMono32.gif");
        }
        return null;
      }

This method is called with a constant, indicating what kind of icon is being requested; for example, BeanInfo.ICON_COLOR_16x16 requests a 16 × 16 color image. If an appropriate icon is available, it loads the image and returns an Image object. If the icon isn’t available, it returns null. For convenience, you can package the images in the same JAR file as the bean and its BeanInfo class.

Though we haven’t used them here, you can also use a BeanInfo object to provide information about other public methods of your bean (for example, array-valued properties) and other features.

Creating customizers and property editors

JavaBeans lets you provide a customizer for your beans. Customizers are objects that do advanced customization for a bean as a whole; they let you provide your own GUI for tweaking your bean. We won’t show you how to write a customizer; it’s not too difficult, but it’s beyond the scope of this chapter. Suffice it to say that a customizer must implement the java.beans.Customizer interface and should extend Component (or JComponent) so that it can be displayed.

Property editors are a way of giving the properties sheet additional capabilities. For example, you could supply a property editor to let you edit a property type that is specific to your bean. You could provide a property editor that would let you edit an object’s price in dollars and cents. We’ve already seen a couple of property editors: the editor used for Color-valued properties is fundamentally the same as a property editor you might write yourself. In addition, the Molecule bean uses a property editor to specify its moleculeName property. A property editor isn’t quite as fancy as a customizer, but describing it fully is also beyond the scope of this chapter.

Again, describing how to write a property editor is also beyond the scope of this chapter. However, it might help you to know that a property editor must implement the PropertyEditor interface; it usually does so by extending the PropertyEditorSupport class, which provides default implementations for most of the methods.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.