Writing property editors

A property editor is an editor for changing property values at design time. You can see several different types of property editors in JBuilder's Component Inspector. For example, for some properties, you simply type in a value in the Component Inspector, and by so doing, change the value of the property. This is the simplest type of property editor. For other properties, you use a choice menu (drop-down list) to display all the possible values and you select the value you want from that list. Colors and fonts have property editors that are actually dialog boxes you can use to set their values.

The JavaBeans Component Library supplies a few property editors for the primitive Java data types. When you create your own components, however, you might create your own property classes and want to have editors capable of editing their values.

This following sections will get you started writing your own property editors. These topics are covered:

Implementing the PropertyEditor interface

Every property editor must implement the PropertyEditor interface. You can implement PropertyEditor directly, or you can start by extending the PropertyEditorSupport class. PropertyEditorSupport implements the PropertyEditor interface.

To begin creating a property editor class, create a class that implements the PropertyEditor interface.

You can use the PropertyEditorSupport class as a starting point, or implement PropertyEditor directly in the class you create. This is the recommended naming convention:

class <propertyName>Editor

Getting a Java initialization string

Each property editor must implement the getJavaInitializationString() method. getJavaInitializationString() returns a string representation of the property value that JBuilder uses in code it generates. This is the getJavaInitializationString() declaration:
public String getJavaInitializationString()

The returned string must be suitable for a Java assignment. Here are some examples:

Selecting a display style

The PropertyEditor interface provides several ways of updating and displaying property values. A property editor seldom needs to support all of them. Property values can display in a property editor as either text or any other object. How property values are displayed determines which methods of the PropertyEditor interface you need to use.

Displaying the value as text

The simplest kind of property editor displays the property value as text and allows the user to change the property value by typing in a new text string. This doesn't mean that the data type of the property must be a string, but that the value is represented by a string.

To permit the display and editing of a property value as text, write code that implements the getAsText() and setAsText() methods. Here are their declarations:

public String getAsText()

public void setAsText(String text) throws IllegalArgumentException

setAsText() throws the IllegalArgumentException exception if the string isn't formatted properly or if the property can't be represented as a text string.

Displaying the value as a list of text strings

Another commonly used property editor type presents the user a list of choices in a choice menu. The user can enter one of these values only, eliminating the possibility of an invalid property data type. For example, the alignment property of JBuilder's ListControl uses a choice menu of values. boolean values are usually presented this way.

To permit the display and editing of a property value as a list of text values,

  1. Write code that implements the getAsText() and setAsText() methods.
  2. Write code that implements the getTags() method.

This is the declaration of getTags():

public String[] getTags()

getTags() returns an array of tags that are used to represent enumerated values of the property. These tags are the strings the user sees in a choice menu (drop-down list). The user chooses the desired value from the list.

Here's an example of a getTags() method that presents the user a choice of beverages:

public String[] getTags() {
    String[] choices = new String {"Coffee", "Tea", "Milk", "Orange juice"};
    return choices;
}

The getAsText() method must be able to display one of the tagged values, and the setAsText() method must be able to set the property's value using one of the tagged values.

Displaying the value as a graphical representation

Property editors can display values in forms other than text. Two methods are used to represent the property value graphically: isPaintable() and paintValue():
public boolean isPaintable()
public void paintValue(Graphics gfx, Rectangle box)

To make your property editor capable of painting a property value,

  1. Implement isPaintable() and have it return true. If the property value uses getAsText() and setAsText(), set isPaintable() to false.
  2. Write the paintValue() method that paints the representation of the property value.
  3. The box argument is the rectanglar area that surrounds the data item in which the painting occurs. The gfx argument is the Graphics object the painting takes place on. If the property editor displays and sets values as text only, leave the body of the paintValue() method empty.

Providing a custom editor

While the three ways of displaying and setting property values will meet most of your needs, there are times when you might want to use more specialized and elaborate property editors. For example, ListControl uses dialog boxes to allow the user to select font attributes for its font property, and to select color attributes for its background and foreground properties.

There are two methods you must write: supportsCustomEditor() and getCustomEditor():

public boolean supportsCustomEditor()
public Component getCustomEditor()

To provide a custom editor for your property editor class,

  1. Implement supportsCustomEditor() and have it return true. If the property editor doesn't use a custom editor, supportsCustomEditor() must return false.
  2. Write the code for the getCustomEditor().

Note that getCustomEditor() returns a Component. You can create just about any kind of property editor you want. You must take on the task of connecting the custom editor with the property editor.

Notifying listeners of changes in a property value

When a property editor is used to change a property value, the editor can generate a PropertyChange event on all registered objects that are interested in a change in the property value. To register and unregister themselves as listeners of property change events, the objects call the addPropertyChangeListener() and removePropertyChangeListener() methods:
public void addPropertyChangeListener(PropertyChangeListener listener)
public void removePropertyChangeListener(PropertyChangeListener listener)

By implementing these methods, the property editor can notify all listeners that a change in a property value has occurred.

See also: Working with events

A property editor example

JBuilder's JavaBeans controls use property editors. You can see them in the Component Inspector. This section presents the code for IntegerTagEditor, the engine that displays property values that evaluate to integers in a choice menu. Next you'll find the code for the TreeStyleEditor, the property editor for the style property of TreeControl. TreeStyleEditor extends the IntegerTagEditor class.
package borland.jbcl.editors;

import java.beans.*;

import borland.jbcl.util.*;

public class IntegerTagEditor implements PropertyEditor
{
  int[] values;               // the array of values (null counts from 0:n)
  String[] resourceStrings;   // strings the user will see in the drop down
  String[] sourceCodeStrings; // strings this will generate in source code

  public IntegerTagEditor(int[] values, String[] resourceStrings, String[] sourceCodeStrings) {
    // A null list of integer values assumes an incrementing enumeration from 0
    if (values == null) {
      values = new int[resourceStrings.length];
      for (int i = 0; i < values.length; ++i)

        values[i] = i;
    }
    this.values = values;
    this.resourceStrings = resourceStrings;
    // A null set of sourceCode Strings assumes the resourceStrings are the same
    this.sourceCodeStrings = (sourceCodeStrings != null) ? sourceCodeStrings : resourceStrings;
  }

  // PropertyEditor Implementation

  public void setValue(Object o) {
    value = o;
    fire();
  }

  public Object getValue() {
    return value;
  }

  public boolean isPaintable() {
    // the property editor does support painting the property value
    return false;                    
  }

  public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
    // Leave empty.             // no painting occurs
  }


  private String getAsText(boolean forSourceCode) {
    int iVal = (value == null || !(value instanceof Integer))
                  ? values[0]
                  : ((Integer)value).intValue();
    int iPos;
    for (iPos = 0; iPos < values.length; ++iPos)
      if (values[iPos] == iVal)
        break;
    if (iPos >= values.length)
      return "";
    return (forSourceCode) ? sourceCodeStrings[iPos] : resourceStrings[iPos];
  }
  
  public String getAsText() {
    // returns one of the strings the user sees in the choice menu
    return getAsText(false);  
  }

  public String getJavaInitializationString() {
    // returns the one of the source code strings
    return getAsText(true);    
  }

  public void setAsText(String text) throws java.lang.IllegalArgumentException {
    int iPos = 0;
    if (text != null) {
      for (; iPos < resourceStrings.length; ++iPos)
        if (text.equals(resourceStrings[iPos]))
          break;
      if (iPos >= resourceStrings.length)
        throw new java.lang.IllegalArgumentException();
      value = new Integer(values[iPos]);

      // generates a PropertyChange event
      fire();    
    }
  }

  public String[] getTags() {
    // returns the strings the user sees in the choice menu
    return resourceStrings;      
  }

  public java.awt.Component getCustomEditor() {
     // no custom editor available
    return null;                       
  }

  public boolean supportsCustomEditor() {
    // doesn't support a custom editor

    return false;                    }

  private void fire() {
    // calls the propertyChange() method in the listener and passes it a PropertyChangeEvent
    if (listener != null) {
      listener.propertyChange(new PropertyChangeEvent(this, "???", null/*???*/, value));  
    }
  }

   addPropertyChangeListener(PropertyChangeListener l) {
    // registers the listener for property change events
    listener = l;                      
  }

  public void removePropertyChangeListener(PropertyChangeListener l) {
    // removes the listener for property change events
    listener = null;               
  }

  private PropertyChangeListener listener;
  private Object value;
}

The TreeStyleEditor class

Several of JBuilder's property editors extend the IntegerTagEditor class. TreeStyleEditor is one of them:
package borland.jbcl.editors;

public class TreeStyleEditor extends IntegerTagEditor
{
  public TreeStyleEditor() {
    // creates the values array
    super(new int[] {
            borland.jbcl.view.TreeView.STYLE_PLUSES,
            borland.jbcl.view.TreeView.STYLE_ARROWS},

          // creates the resourceStrings array
          new String[] {
            "Pluses",
            "Arrows"},

          // creates the array that holds the strings JBuilder uses when generating source code
          new String[] {
            "borland.jbcl.view.TreeView.STYLE_PLUSES", 
            "borland.jbcl.view.TreeView.STYLE_ARROWS"}); 
  }
}

Specifying a property editor for a property

After you've created your property editor for your property, you must specify the name of the property editor in the component's BeanInfo class. For information on how to do this, see Specifying a customized editor for a property.

JBuilder provides several default property editors that are based on property types. If you fail to specify a property editor for a property in the BeanInfo class, JBuilder attempts to use one of these default editors. For example, it supplies a string type editor for entering and editing a string value such as the value of the text property for LabelControl. Another example is the int type editor for entering and editing integer values such as the value of the itemWidth property of ListControl.

If JBuilder can't find an editor that can edit the property type, it uses a scoped object list editor, which displays each of the objects in scope that match the given property type. For example, the dataSet property for the various JBuilder controls use the scoped object list editor and lists all the objects in scope of the type DataSet.


Creating component customizers

The property editors you develop for your component can edit a single property at a time. Customizers go a step further. A customizer can edit the entire component at once. Usually a customizer presents a dialog box or panel that lets the user set many properties at once. A customizer can even provide an interactive interface to the component user, guiding the user through the steps to customize the component. For example, you could create wizard that asks the user questions about how they want the component customized. Based on the user's responses, the customizer can edit all affected properties of the component. Whenever a customizer generates an event informing JBuilder something happened, JBuilder generates the appropriate code.

Within the customizer, you must write the code that modifies the properties of your component. In addition, your customizer must do these things:

Your customizer will appear as a modal dialog box that contains an OK and a CANCEL button. The user must finish using the customizer before continuing with any other task.

Implementing the Customizer interface

To create a customizer for your component, create a class that implements the Customizer interface. The name of the class must be the same as the name of the component it customizes with "Customizer" appended to it. For example, if your component is named WizzyBox, the customizer must be named WizzyBoxCustomizer.

Customizer contains three methods you must implement:

Specifying the component to customize

The setObject() method specifies the component the customizer is customizing. For example,
public class CoolComponentCustomizer extends BevelPanel
                                     implements Customizer {

  protected CoolComponent bean;
  ...

  public void setObject(Object obj) {
  bean = (CoolComponent)obj;
  ...
)

Notifying listeners of component changes

When a user uses the customizer to make changes to a component, the customizer should send a PropertyChange event to all to all listeners interested in the changes. To register and unregister themselves as listeners of property change events, the listeners call the addPropertyChangeListener() and removePropertyChangeListener() methods:
public void addPropertyChangeListener(PropertyChangeListener listener)
public void removePropertyChangeListener(PropertyChangeListener listener)

By implementing these methods, the customizer can notify all listeners that a change in a property value has occurred.

Note:
Some of the early customizers available neglected to identify the property being changed. It is vital that your customizer do so, however. Your customizer must contain the code that changes the property values in the component. For each property changed, a PropertyChange event should be fired, notifying all registered listeners the property change occurred.

Saving customizer changes

When you create a property editor for a bean's property editor, JBuilder derives the information it needs from your PropertyEditor implementation to generate the Java code for the property change. The next time the bean is used, even in another session, the property will have the changed value.

Customizers, however, change only the live instance of the bean. Usually this means that JBuilder does not have the information to generate Java source code to reflect the changes. You must take additonal steps if you want the changes made with the customizer to remain in effect the next time the bean is used.

To save the changes made with a customizer, serialize the file containing the bean. For more information on serialization, see Serializing JavaBeans.

Modifying the BeanInfo class

In the component's BeanInfo class, specify the name of the customizer for the component. You can do this in the getBeanDescriptor() method:
public BeanDescriptor getBeanDescriptor() {
  return new BeanDescriptor(CoolComponent.class, CoolComponentCustomizer.class);
}

If you use the BasicBeanInfo class to create the BeanInfo class for your component, assign the customizer class to the beanCustomizer variable. For example,

public class CoolComponentBeanInfo extends BasicBeanInfo {
  public CoolComponentBeanInfo() {
    beanClass = CoolComponent.class;                   // specify the component
    customizerClass = CoolComponentCustomizer.class;   // specify the customizer
    propertyDescriptors = new String[][]{
      ...
    }
  }
}

Note:
If your customizer makes changes to the bean other than public property modifications, add this statement to the bean's BeanInfo class:

BeanDescriptor.setValue("hidden-state", Boolean.True)
This statement tells JBuilder that it won't be able to duplicate in code the changes made to the bean. When JBuilder detects this statement, it displays a warning message on its status bar. JBuilder does not generate any code, regardless of whatever events occurred.

For more information about using BeanInfo classes, see Specifying component information with BeanInfo classes.