A bean may want to provide some way for the user of a beanbox program to customize its properties other than by setting them one at a time. A bean can do this by creating a Customizer class for itself, and registering the customizer class with the BeanDescriptor object returned by its BeanInfo class, as we saw in Example 10.5.
A customizer must be some kind of AWT component that is suitable for display in a dialog box created by the beanbox. Therefore, a customizer class is typically a subclass of Panel. In addition, a customizer must implement the Customizer interface. This interface consists of methods for adding and removing property change event listeners and a setObject() method that the beanbox calls to tell the customizer what bean object it is customizing. Whenever the user makes a change to the bean through the customizer, the customizer should send a PropertyChangeEvent to any interested listeners. Finally, like a property editor, a customizer must have a no-argument constructor, so it can easily be instantiated by a beanbox.
Example 10.8 shows a customizer for our YesNoDialog bean. This customizer displays a panel that has the same layout as a YesNoDialog, but it substitutes a TextArea object for the message display and three TextField objects for the three buttons that the dialog can display. These text entry areas allow the user to enter values for the message, yesLabel, noLabel, and cancelLabel properties. Figure 10.3 shows this customizer panel displayed within a dialog box created by the beanbox program. Again, note that the Done button is part of the beanbox dialog, not part of the customizer itself.
package oreilly.beans.yesno; import java.awt.*; import java.awt.event.*; import java.beans.*; /** * This class is a customizer for the YesNoDialog bean. It displays a * TextArea and three TextFields where the user can enter the dialog message * and the labels for each of the three buttons. It does not allow the * dialog title or other resources to be set. */ public class YesNoDialogCustomizer extends Panel implements Customizer, TextListener { protected YesNoDialog bean; // The bean being customized. protected TextComponent message, fields[]; // Components used by customizer // Default constructor: YesNoDialogCustomizer() { super(); } // The bean box calls this method to tell us what object to customize. // This method will always be called before the customizer is displayed, // so it is safe to create the customizer GUI here. public void setObject(Object o) { bean = (YesNoDialog)o; // Save the object we're customizing. // Put a label at the top of the panel. this.setLayout(new BorderLayout()); this.add(new Label("Enter the message to appear in the dialog:"), "North"); // And a big text area below it for entering the dialog message. message = new TextArea(bean.getMessage()); message.addTextListener(this); // TextAreas don't know how big they want to be. You must tell them. message.setSize(400, 200); this.add(message, "Center"); // Then add a row of textfields for entering the button labels. Panel buttonbox = new Panel(); // The row container. buttonbox.setLayout(new GridLayout(1, 0, 25, 10)); // Equally spaced items. this.add(buttonbox, "South"); // Put row on bottom. // Now go create three TextFields to put in this row. But actually // position a Label above each, so create a container for each // TextField+Label combination. fields = new TextComponent[3]; // Array of TextFields. String[] labels = new String[] { // Labels for each. "Yes Button Label", "No Button Label", "Cancel Button Label"}; String[] values = new String[] { // Initial values of each. bean.getYesLabel(), bean.getNoLabel(), bean.getCancelLabel()}; for(int i = 0; i < 3; i++) { Panel p = new Panel(); // Create a container. p.setLayout(new BorderLayout()); // Give it a BorderLayout. p.add(new Label(labels[i]), "North"); // Put a label on the top. fields[i] = new TextField(values[i]); // Create the text field. p.add(fields[i], "Center"); // Put it below the label. fields[i].addTextListener(this); // Set the event listener. buttonbox.add(p); // Add container to row. } } // Add some space around the outside of the panel. public Insets getInsets() { return new Insets(10, 10, 10, 10); } // This is the method defined by the TextListener interface. Whenever the // user types a character in the TextArea or TextFields, this will get // called. It updates the appropriate property of the bean and fires a // property changed event, as all customizers are required to do. // Note that we are not required to fire an event for every keystroke. // Instead we could include an "Apply" button that would make all the // changes at once, with a single property changed event. public void textValueChanged(TextEvent e) { TextComponent t = (TextComponent)e.getSource(); String s = t.getText(); if (t == message) bean.setMessage(s); else if (t == fields[0]) bean.setYesLabel(s); else if (t == fields[1]) bean.setNoLabel(s); else if (t == fields[2]) bean.setCancelLabel(s); listeners.firePropertyChange(null, null, null); } // This code uses the PropertyChangeSupport class to maintain a list of // listeners interested in the edits we make to the bean. protected PropertyChangeSupport listeners = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener l) { listeners.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { listeners.removePropertyChangeListener(l); } }