We'll conclude this survey of Swing features with a quick look at what it takes to write a custom Swing component. Creating a custom component is a matter of subclassing an existing component and adding the new functionality you desire. Sometimes this is a simple job of adding a minor new feature to an existing component. At other times, you may want to create an entirely new component from scratch. In this case, you'll probably be subclassing JComponent, which is a bit more complicated. The following sections briefly explain the various things you'll need to consider when creating such a custom component. The best way to learn to write your own Swing-style components is to study the source code of Swing components, and since Sun makes this source code freely available, I encourage you to examine it.
You need to decide what properties you want your component to export and define accessor methods that allow them to be set and queried. If your component represents or displays some kind of nontrivial data structure, consider representing the data in a separate model object. Define an interface for the model and a default implementation of the interface.
If you think that other objects may be interested in property changes on your component, have the set methods for those properties generate the events PropertyChangeEvent or ChangeEvent and include appropriate event listener registration methods in your component. This kind of notification is often important if you follow the Swing architecture and divide the functionality of your component among a component object, a model object, and a UI delegate object.
When a property is set on your component, the component may need to be redrawn or resized as a result. You must keep this in mind when you write the property accessor methods for your component. For example, if you define a setColor() method, this method should call repaint() to request that the component be repainted. (Painting the component is a separate topic that is discussed later.) If you define a setFont() method and a change in font size causes the component to require more (or less) space on the screen, you should call revalidate() to request a relayout of the GUI. Note that the repaint() and revalidate() methods add a repaint or relayout request to a queue and return right away. Therefore, you may call these methods freely without fear of inefficiency.
You need to decide what kind of events your component generates. You can reuse existing event and listener classes, if they suit your purposes, or you can define your own. Add event listener registration and deregistration methods in your component. You need to keep track of the registered listeners, and you may find the javax.swing.event.EventListenerList helpful for this task. For each event listener registration method, it is common practice to define a protected method to generate and fire an appropriate event to all registered listeners. For example, if your component has a public addActionListener() method, you may find it useful to define a protected fireActionEvent() method as well. This method calls the actionPerformed() method of every registered ActionListener object.
It is customary to provide a no-argument constructor for a component. This is helpful if you want your component to work with GUI builder tools, for example. In addition, think about how you expect programmers to use your component. If there are a few properties that are likely to be set in most cases, you should define a constructor that takes values for these properties as arguments, to make the component easier to use.
Almost every component has some visual appearance. When you define a custom component, you have to write the code that draws the component on the screen. There are several ways you can do this. If you are creating an AWT component, override the paint() method and use the Graphics object that is passed to it to do whatever drawing you need to do.
For Swing components, the paint() method is also responsible for drawing the border and the children of your component, so you should not override it directly. Instead, override the paintComponent() method. This method is passed a Graphics object, just as the paint() method is, and you use this Graphics object to do any drawing you want. As we'll see in Chapter 4, "Graphics with AWT and Java 2D", you can cast this Graphics object to a Graphics2D object if you want to use Java 2D features when drawing your component. Keep in mind, however, that a Swing component can be assigned an arbitrary border. Your paintComponent() method should check the size of the border and take this value into account when drawing.
When you define a custom component, you typically have only one look-and-feel in mind, so you can hardcode this look-and-feel as part of the component class itself. If you want your component to support the Swing pluggable look-and-feel architecture, however, you need to separate the drawing and event-handling tasks out into a separate javax.swing.plaf.ComponentUI object. If you do this, you should not override your component's paintComponent() method. Instead, put the painting functionality in the paint() method of the ComponentUI implementation. In order to make this work, you have to override the getUIClassID(), getUI(), setUI(), and updateUI() methods of JComponent.
Most components have some kind of interactive behavior and respond to user-input events such as mouse clicks and drags and key presses. When you are creating a custom component, you must write the code that handles these events. The Swing event-handling model was discussed in Chapter 2, "Swing and AWTArchitecture". Recall that the high-level way to handle input events is to register appropriate event listeners, such as MouseListener, MouseMotionListener, KeyListener, and FocusListener on your component. If you are using a separate UI delegate object, this object should implement the appropriate listener interfaces, and it should register itself with the appropriate event registration methods on the component when its installUI() method is called.
If you are not using a UI delegate, your component class can handle events at the lower level discussed in Chapter 2, "Swing and AWTArchitecture". To do this, you override methods such as processMouseEvent(), processMouseMotionEvent(), processKeyEvent(), and processFocusEvent(). In this case, be sure to register your interest in receiving events of the appropriate type by calling enableEvents() in your component's initialization code.
Most components have a natural or preferred size that often depends on the settings of various component properties. Many components also have a minimum size below which they cannot adequately display themselves. And some components have a maximum size they wish to enforce. You must write the methods that compute and return these sizes.
If you are using a UI delegate object, you should implement the getMinimumSize(), getPreferredSize(), and getMaximumSize() methods in the delegate. The default JComponent methods call the delegate methods to determine these sizes if the programmer using the component has not overridden the minimum, preferred, or maximum sizes with her own specifications.
If you are not using a UI delegate object, you should override these three methods in the component itself. Ideally, your methods should respect any sizes passed to setMinimumSize(), setPreferredSize() and setMaximumSize(). Unfortunately, the values set by these methods are stored in private fields of JComponent, so you typically have to override both the get and the set methods.
It is a good idea to make your component accessible. In order to do this, your component must implement the javax.accessibility.Accessible interface and its getAccessibleContext() method. This method must return an AccessibleContext object that is customized for your component. You typically implement AccessibleContext as an inner class of the component by extending JComponent.AccessibleJComponent or some subclass of that class. Depending on your component, you may need to implement various other accessibility interfaces on this inner class as well. Studying the accessibility code in existing Swing components can be very helpful in learning how to write your own accessible components. You might start, for example, with the source code for AbstractButton.AccessibleAbstractButton.
JComponent defines a number of other methods that you can optionally override to change aspects of a component's behavior. If you take a look at the list of properties defined by the JComponent API, you'll notice that a number of these are read-only properties (i.e., they do not define set methods). The only way to set the value returned by one of these methods is to subclass the method. In general, when you see a read-only property, you should consider it a candidate for subclassing. Here are a few methods of particular interest:
If the component always fills its entire background, this method should return true. If a component can guarantee that it completely paints itself, Swing can perform some drawing optimizations. JComponent actually does define a setOpaque() method for this property, but your custom component may choose to ignore setOpaque() and override isOpaque().
If your component has children and allows those children to overlap, it should override this method to return false. Otherwise, leave it as is.
If your component wants to be included in focus traversal, it should override this method to return true. If your component does not want to be included in the keyboard navigation system, this method should return false.
If your component has children and wants to cycle focus among them, override this method to return true.
If your component needs to receive the Tab and Shift-Tab key events that are normally handled by the focus manager, override this method to return true. If you do, the focus manager uses Ctrl-Tab instead.
Copyright © 2001 O'Reilly & Associates. All rights reserved.