3.5 The JComponent Class


JComponent is an abstract class that almost all Swing components extend; it provides much of the underlying functionality common throughout the Swing component library. Just as the java.awt.Component class serves as the guiding framework for most of the AWT components, the javax.swing.JComponent class serves an identical role for the Swing components. We should note that the JComponent class extends java.awt.Container (which in turn extends java.awt.Component), so it is accurate to say that Swing components carry with them a great deal of AWT functionality as well.

Because JComponent extends Container, many Swing components can serve as containers for other AWT and Swing components. These components may be added using the traditional add( ) method of Container. In addition, they can be positioned with any Java layout manager while inside the container. The terminology remains the same as well: components that are added to a container are said to be its children; the container is the parent of those components. Following the analogy, any component that is higher in the tree is said to be its ancestor , while any component that is lower is said to be its descendant.

Recall that Swing components are considered "lightweight." In other words, they do not rely on corresponding peer objects within the operating system to render themselves. As we mentioned in Chapter 1, lightweight components draw themselves using the standard features of the abstract Graphics object, which not only decreases the amount of memory each component uses but allows components to have transparent portions and take on nonrectangular shapes. And, of course, lightweight components are free of a dedicated L&F.

It's not out of the question to say that a potential benefit of using lightweight components is a decrease in testing time. This is because the functionality necessary to implement lightweight components in the Java virtual machine is significantly less than that of heavyweight components. Heavyweight components must be individually mapped to their own native peers. On the other hand, one needs to implement only a single lightweight peer on each operating system for all the Swing components to work correctly. Hence, there is a far greater chance that lightweight components will execute as expected on any operating system and not require rounds of testing for each platform.

Because all Swing components extend Container, you should be careful that you don't add( ) to Swing components that aren't truly containers. The results range from amusing to destructive.

In JDK 1.2, JComponent reuses some of the functionality of the java.awt.Graphics2D class. This consists primarily of responsibilities for component painting and debugging.

3.5.1 Inherited Properties

Swing components carry with them several properties that can be accessed through JComponent but otherwise originate with AWT. Before we go any further, we should review those properties of java.awt.Container and java.awt.Component that can be used to configure all Swing components. This discussion is relatively brief; if you need a more thorough explanation of these AWT classes, see Java AWT Reference by John Zukowski (O'Reilly), which can be downloaded from this book's web site, http://www.oreilly.com/catalog/jswing2/. Table 3-5 lists the properties that JComponent inherits from its AWT superclasses.

Table 3-5. Properties inherited from the AWT Component and Container classes

Property

Data type

get

is

set

Default value (if applicable)

background

Color

·

 

·

 

colorModel

ColorModel

·

   
 

componenti

Component

·

   
 

componentCount

int

·

   
 

components

Component[]

·

   
 

cursor

Cursor

·

 

·

Cursor.DEFAULT_CURSOR

enabled

boolean

 

·

·

true

font

Font

·

 

·

 

foreground

Color

·

 

·

 

insets

Insets

·

   

Insets(0,0,0,0)

layout

LayoutManager

·

 

·

BorderLayout( )

locale

Locale

·

 

·

 

location

Point

·

 

·

 

locationOnScreen

Point

·

 

·

 

name

String

·

 

·

""

parent

Container

·

 

·

null

size

Dimension

·

 

·

 

showing

boolean

 

·

 

true

valid

boolean

 

·

   

visible

boolean

 

·

·

true

iindexed

Let's discuss these properties briefly. The background and foreground properties indicate which colors the component uses to paint itself. We should mention that with Swing the background property is disabled if the component is transparent (not opaque). The read-only colorModel property returns the current model used to translate colors to pixel values; generally, the user does not need to access this property. The font property lets you get or set the font used for displaying text in the component.

The indexed component property maintains a list of all the components inside the container. You can tell how many there are with the integer componentCount property. If you want to access all of them through a Component array, retrieve the components property. The insets property specifies the current insets of the container, while the layout property indicates which layout manager is managing the components of the container. Technically, this means that you can use any component as a container. Don't be misled; if a component doesn't seem like a reasonable container, it probably can't be used as one. (Don't, for example, try to add a JButton to a JScrollBar.) A number of components use these properties for internal, specialized layout managers and components.

The locale property specifies the internationalization locale for the application. The location property indicates the x,y coordinates of the component's upper-left corner in the container's coordinate space. If you want to see the location of the component's upper-left corner in screen coordinates, use the read-only locationOnScreen property.

The name property gives this component a string-based name that components can display if they choose. The parent property references the container that is acting as this component's parent, or null if there is none. The size property specifies the component's current height and width in pixels.

The showing property indicates whether the component is currently showing on the screen, while the visible property tells if the component is marked to be drawn on the screen. There's an odd, nonintuitive relationship between visible and showing. A component that is visible isn't necessarily showing. "Visible" means that a component is capable of being displayed; "showing" means that the component is actually displayed (though it may be obscured by something else). Most containers (JPanel, JFrame, etc.) are invisible by default; most other components (JButton, etc.) are visible by default. So if you add a JButton to an invisible JFrame, for example, the button is visible but not showing. It can be displayed but happens to be in a container that isn't currently displayed.

Finally, if the valid property is false, the component needs to be resized or moved by the component's layout manager. If it is true, the component is ready to be displayed.

3.5.2 Common Methods

Here are some other frequently called methods for working with Swing components:

public Component add(Component comp)
public Component add(Component comp, int index)
public void add(Component comp, Object constraints)
public void add(Component comp, Object constraints, int index)

Add a component to the container, given the optional constraints and the current index.

public void remove(int index)
public void remove(Component comp)
public void removeAll( )

Remove the appropriate component from the container. The final method empties the entire container.

public void pack( )

This method of java.awt.Window resizes the window to encompass the preferred size of all the contained components, as placed by the current layout manager. It's a good idea to call pack( ) after you've added components to a top-level container with a layout manager, such as JFrame, JApplet, JDialog, and JWindow.

public void validate( )
public void invalidate( )

The invalidate( ) method is typically called on a Container to indicate that its children need to be laid out, or on a Component to indicate that it needs to be re-rendered. This method is often called automatically. However, certain changes to a Component (such as changing the size of a button by changing its label or font) do not cause it to be invalidated. In such cases, invalidate( ) must be called on the Component to mark it as invalid, and validate( ) must be called on its Container. The validate( ) method is typically called to validate, lay out, and repaint a Container. Calling this method is especially important when you add or remove Components in a Container that is already displayed.

Swing improves the validate( )/invalidate( ) situation a bit by calling invalidate( ) in response to many property changes, saving you from having to make the call. Unfortunately, there are still situations (such as changing a JButton's font) that do not trigger an automatic invalidate( ) call, so you'll still have to explicitly call invalidate( ) in these cases.

The key things to take away from these methods are:

  • You may need to call invalidate( ) if you make changes to the appearance of a displayed component.

  • You must call validate( ) on Containers that have been invalidated (typically by the addition or invalidation of a child).

As a result of deprecation and the movement toward JavaBeans accessors, AWT has some methods with multiple names. For example, show( ) and setVisible(true) are essentially the same. It is always better to use the JavaBeans-style name setVisible( ) in this case when working with Swing; the newer name is less confusing for people familiar with the JavaBeans conventions.

3.5.3 JComponent Properties

Now to the heart of the matter. JComponent has many properties of its own and overrides (or otherwise modifies) the behavior of many of its inherited properties. This is where the new and interesting stuff happens. Table 3-6 shows a summary of JComponent's properties.

Table 3-6. JComponent properties

Property

Data type

get

is

set

Default value

UIb, p

ComponentUI

   

·

 

UIClassID

String

·

   

"ComponentUI"

accessibleContext

AccessibleContext

·

   

null

actionMap1.3

ActionMap

·

 

·

 

alignmentXo

float

·

 

·

 

alignmentYo

float

·

 

·

 

ancestorListeners1.4

AncestorListener[]

·

     

autoscrolls

boolean

·

 

·

false

borderb

Border

·

 

·

null

boundso

Rectangle

·

 

·

 

debugGraphicsOptions

int

·

 

·

DebugGraphics.NONE_OPTION

defaultLocales1.4

Locale

·

 

·

 

doubleBuffered

boolean

 

·

·

false

enabledb,o

boolean

 

·

·

true

focusCycleRoot

boolean

 

·

 

false

focusTraversabled

boolean

 

·

 

true

graphicso

Graphics

·

     

height

int

·

   

bounds.height

inputMapf, 1.3; also indexed get

InputMap

·

 

·

 

inputVerifierb, 1.3

InputVerifier

·

 

·

 

insetso

Insets

·

 

·

 

locationo

Point

·

 

·

Point(bounds.x, bounds.y)

managingFocusd

boolean

 

·

 

false

maximumSizeb, o

Dimension

·

 

·

 

minimumSizeb, o

Dimension

·

 

·

 

nextFocusableComponentd

Component

·

 

·

 

opaqueb

boolean

 

·

·

false

optimizedDrawingEnabled

boolean

 

·

 

true

paintingTile

boolean

 

·

   

preferredSizeb, o

Dimension

·

 

·

 

propertyChangeListeners (also string indexed version)

PropertyChangeListener[]

·

     

registeredKeyStrokes

KeyStroke[]

·

     

requestFocusEnabled

boolean

 

·

·

true

rootPane

JRootPane

·

     

sizeo

Dimension

·

 

·

Dimension (bounds.height, bounds.width)

toolTipText

String

·

 

·

null

topLevelAncestor

Container

·

     

transferHandlerb,1.4

TransferHandler

·

 

·

null

validateRoot

boolean

 

·

 

false

verifyInputWhenFocusTargetb, 1.3

boolean

·

 

·

 

vetoableChangeListeners1.4

VetoableChangeListener[]

       

visibleo

boolean

 

·

·

true

visibleRect

Rectangle

·

     

widtho

int

·

   

bounds.width

xo

int

·

   

bounds.x

yo

int

·

   

bounds.y

1.3since 1.3,1.4since 1.4, bbound, ddeprecated, ffinal, iindexed, ooverridden, pprotected, sstatic

See also java.awt.Container and java.awt.Component (Table 3-5).

3.5.3.1 New properties in the 1.3 and 1.4 SDKs

The properties added in 1.3 and 1.4 focus on three areas. The InputMap and ActionMap classes were added in 1.3 to improve the handling of keyboard events. (These classes are discussed in Section 3.5.14 later in this chapter.) SDK 1.4 added some convenience support for accessing event handlers such as property and vetoable property change listeners. The transferHandler property was also added in 1.4 a big step up in the usability of Drag and Drop (DnD). You can learn more about that property in Chapter 24, which is devoted to DnD functionality.

Finally, the inputVerifier and verifyInputWhenFocusTarget properties were added in 1.3 to offer applications an easy and reliable way to check a user's text input for validity. Text components with attached InputVerifiers will call the verifier's shouldYieldFocus( ) method when they're about to lose input focus, providing an opportunity to give the user feedback and keep focus if the input isn't valid. Any Components, such as Cancel buttons, that should remain usable even when there is invalid input in some text field, can be configured to work properly by setting their verifyInputWhenFocusTarget property to false. These capabilities are discussed in greater depth in Chapter 20.

3.5.4 UI Delegates and UIClassIDs

As we mentioned in Chapter 1, all Swing components use a modified MVC architecture. Each Swing component is responsible for maintaining two unique objects: a model and a UI delegate. The object representing the model handles the state information specific to the component while the UI delegate determines how the component paints itself based on the model's state information.

Note that there is no property for a model in JComponent. You typically access the model property at the level of a JComponent subclass. This is because each Swing component defines its own data model, which is unique from that of all other components. The UI delegate property, on the other hand, can be handled at the JComponent level because the methods for rendering lightweight components are always the same. These methods (e.g., installUI( ), uninstallUI( ), setUI( ), paint( )) can be traced back to the abstract class javax.swing.plaf.ComponentUI, which serves as the superclass for all UI delegates.

JComponent contains a reference to the current UI delegate for the object. JComponent allows a subclass to alter the component's UI delegate with the protected setUI( ) method; this method effectively resets the L&F of the component. The UI therefore acts like a write-only property, but we hesitate to call it a property because its mutator isn't public. Invoking setUI( ) by itself, however, does not change the display. A call to updateUI( ) is also required, which forces the component to redraw itself. If you are looking to change the entire L&F of the application, it is better to change it universally with the setLookAndFeel( ) method of UIManager than to change it one component at a time. See Chapter 2 for a simple example of how to work with various L&Fs.

Each Swing component maintains a read-only string constant, UIClassID, that identifies the type of UI delegate that it uses. Most Swing components override the accessor getUIClassID( ) and return a string constant, typically the letters "UI" appended to the name of the component (without the "J"). This string is then used by Swing's UI manager to match the component with a UI delegate for the current L&F. For example, a JButton object has a UIClassID string of ButtonUI. If the current L&F is Metal, the UIManager can figure out that the MetalButtonUI is the correct UI-delegate class to use. See Chapter 26 for more information about the UIManager and how to change L&Fs.

3.5.5 Invalidating and Repainting

Sometimes entire components need to be drawn to the screen. At other times, only parts of components can (or should) be drawn. For example, if an internal frame is dragged across the container, the entire internal frame is redrawn along the way until it reaches its destination. However, only the parts of the container uncovered by the internal frame need to be repainted. We typically do not repaint the entire component, as this would be an unnecessary waste of processing time. (See Figure 3-4.)

Figure 3-4. Performing repaints for components in Java
figs/swng2.0304.gif

Swing uses a repaint manager to repaint lightweight components. The repaint manager maintains a queue of rectangular areas that need to be repainted; it calls these areas "dirty regions." Sometimes the rectangles are the size of entire components; at other times they are smaller. The repaint manager processes repaint requests as they are added to the queue, updating dirty regions as quickly as possible while preserving the visual order of the components. In AWT, the Component class contains an overloaded repaint( ) method that allows you to repaint only a subrectangle of the component. The same is true with JComponent. If only part of a component needs to be repainted, the repaint manager invokes an overloaded version of the repaint( ) method that takes a Rectangle parameter.

JComponent contains two repaint( ) methods that add specified rectangles directly to the dirty region. Like AWT, you should call these methods instead of invoking the paint( ) method directly, which bypasses the RepaintManager. The RepaintManager class is discussed in more detail in Chapter 28.

3.5.5.1 The paint( ) method and opaqueness

Because JComponent is the direct subclass of the AWT Container class, it is the official recipient of repaint requests through its paint( ) method. As you might guess, JComponent must delegate this request by passing it to the paint( ) method of the UI-delegate object. The responsibility, however, does not end there. JComponent is actually responsible for painting three items: the component itself, any borders associated with the component, and any children that it contains.

The order is intentional. Components drawn last are always on top; hence, child components always paint over their parents. JComponent contains three protected methods that it uses to complete this functionality:

  • protected void paintComponent(Graphics g)

  • protected void paintBorder(Graphics g)

  • protected void paintChildren(Graphics g)

Because of the complexity involved in painting and repainting Swing components, you should always try to override these three methods while creating your own components. Also, do not try to override paint( ) unless you call super.paint( ).

SDK 1.4 introduced a series of methods relating to printing rather than painting. Calling the print( ) or printAll( ) methods (both public and available since 1.2) now results in calls to printComponent( ), printBorder( ), and printChildren( ) in that order.

When painting or printing JComponent subclasses, the Graphics object passed to these methods is actually a Graphics2D object. You can cast it as such if you want to take advantage of the increased functionality available in the 2D packages. Check out Jonathan Knudsen's Java 2D Graphics (O'Reilly) for more detailed information.

The boolean property opaque dictates the transparency of each Swing object.[1] If this property is set to false, the component's background color is transparent. This means that any areas left untouched by the component's rendering allow graphics in the background to show through. If the property is set to true, the rectangular painting region is completely filled with the component's background color before it is rendered. Incidentally, transparency was not possible before lightweight components. Native peer objects in Java 1.0 always drew their component on a solid rectangle; anything that was behind the component was erased. Figure 3-5 shows the difference between an opaque and a transparent (nonopaque) label, both with a dark background color. The label on the left is transparent, so its background color is ignored; the label's text appears on top of the container's relatively light background.

[1] In JDK 1.2, the isOpaque( ) method is defined in java.awt.Component.

Figure 3-5. Transparency and opaqueness
figs/swng2.0305.gif

JComponent can optimize its repainting time if none of its children overlap; this is because the repaint manager does not have to compute the hidden and visible areas for each child component before rendering them. Some containers, such as JSplitPane, are designed so that overlap between child components is impossible, so this optimization works nicely. Other containers, such as JLayeredPane, have support for child components that can overlap. JComponent contains a property that Swing frequently calls upon to see if it can optimize component drawing: optimizedDrawingEnabled. In JComponent, this property is set to true by default. If overlap occurs in a subclass of JComponent, the subclass should override the isOptimizedDrawingEnabled( ) accessor and return false. This prevents the repaint manager from using the optimized drawing process when rendering the container's children.

JComponent contains a boolean read-only property (paintingTile) that indicates whether the component is currently in the process of painting a tile , which is a child component that does not overlap any other children. The isPaintingTile( ) method returns true until all tiles have been painted.

The visibleRect property is a Rectangle that indicates the intersection of the component's visible rectangles with the visible rectangles of all of its ancestors. Why the intersection? Remember that you can have a contained object that is clipped by its parent. For example, you can move an internal frame so that a portion of it falls outside the parent window's clipping region. Therefore, the visible portion (the portion that is actually drawn to the screen) consists only of the intersection of the parent's visible portion and the child's visible portion. You typically do not need to access this property.

The validateRoot property is false by default. If it is set to true, it designates this component as the root component in a validation tree. Recall that each time a component in a container is invalidated, its container is invalidated as well, along with all of its children. This causes an invalidation to move all the way up the component hierarchy, stopping only when it reaches a component for which isValidateRoot( ) returns true. Currently, the only components that set this property to true are JRootPane (which is used by all the Swing top-level components), JScrollPane, and JTextField.

The topLevelAncestor property contains a reference to the top-level window that contains this component, usually a JWindow or JApplet. The rootPane property contains the low-level JRootPane for this component; JRootPane is covered in more detail in Chapter 8.

Finally, JComponent contains a property called autoscrolls , which indicates whether a component is capable of supporting autoscrolling. This property is false by default. If the property is true, an Autoscroller object has been set over this component. The Autoscroller object monitors mouse events on the target component. If the mouse is dragged outside the component, the autoscroller forces the target component to scroll itself. Autoscrolling is typically used in containers such as JViewport.

3.5.6 Position, Size, and Alignment

You can set and retrieve a Swing component's current position and size on the screen through the bounds property, or more precisely, through the location and size properties of JComponent. The location property is defined as a Point in the parent's coordinate space where the upper-left corner of the component's bounding box resides. The size property is a Dimension that specifies the current width and height of the component. The bounds property is a Rectangle object that gives the same information: it bundles both the location and the size properties. Figure 3-6 shows how Swing measures the size and location of a component.

Figure 3-6. Working with the bounds, size, and location properties
figs/swng2.0306.gif

Unlike the AWT Component class, the getBounds( ) accessor in JComponent can take a preinstantiated Rectangle object:

Rectangle myRect = new Rectangle( ); myRect = component.getBounds(myRect);

If a Rectangle is supplied, the getBounds( ) method alters each of the fields in the passed-in Rectangle to reflect the component's current size and position, returning a copy of it. If the reference passed in is a null, the method instantiates a new Rectangle object, sets its values, and returns it. You can use the former approach to reduce the number of garbage rectangles created and discarded over multiple calls to getBounds( ), which increases the efficiency of your application.

The setBounds( ) method alters the component's size and position. This method also takes a Rectangle object. If the new settings are different from the previous settings, the component is moved, typically resized, and invalidated. If the component has a parent, it is invalidated as well. Be warned that various layout managers may override any changes you attempt to make to the bounds property. Invalidating a component with a call to setBounds( ) may force the layout manager to recompute and reset the bounds of the component in relation to the other components, resolving it to the same size as before.

Here is a short example that shows how to retrieve the current position and size of any Swing component:

JFrame frame = new JFrame("Test Frame"); frame.setBounds(20,20,200,200); frame.setVisible(true); Rectangle r = new Rectangle( ); r = frame.getBounds(r); System.out.println("X      = " + r.x( )); System.out.println("Y      = " + r.y( )); System.out.println("Width  = " + r.width( )); System.out.println("Height = " + r.height( ));

There is a shorthand approach for retrieving each of the bounds properties. JComponent contains four methods that directly access them: getX( ), getY( ), getWidth( ), and getHeight( ). You can use these accessors directly instead of instantiating a Rectangle object on the heap with a call to getBounds( ). Consequently, you can replace the last six lines with the following four:

System.out.println("X      = " + frame.getX( )); System.out.println("Y      = " + frame.getY( )); System.out.println("Width  = " + frame.getWidth( )); System.out.println("Height = " + frame.getHeight( ));

In addition, if it is just the size or location you are concerned with, you can use the getSize( ) and getLocation( ) accessors to set or retrieve the size or location. Size is specified as a Dimension while location is given as a Point. Like getBounds( ), the getLocation( ) accessor also allows the programmer to pass in a preinstantiated Point object. If one is passed in, the method alters the coordinates of the Point instead of instantiating a new object.

Point myPoint = new Point( ); myPoint = component.getLocation(myPoint);

You can still use the setSize( ) and setLocation( ) methods of java.awt.Component if you prefer to code with those as well. Again, note that when altering the size of the component, the layout manager may override the new value and reset it to its previous value, thus ignoring your new size values.

The three well-known AWT sizing properties, minimumSize, preferredSize, and maximumSize, are accessible through JComponent. minimumSize indicates the smallest size for the component when it is in a container. preferredSize contains the size at which the container's layout manager should strive to draw the component. maximumSize indicates the largest size the component should be when displayed in a container. If none of these properties are set by the user, they are always calculated by the component's UI delegate or directly by the layout manager of the container, in that order. The methods setMinimumSize( ), setPreferredSize, and setMaximumSize( ) allow you to change these properties without subclassing.

Finally, JComponent contains two read/write properties that help interested layout managers align the component in a container: alignmentX and alignmentY. Both of these properties contain floating-point values between 0.0 and 1.0; the numbers determine the position of the component relative to any siblings. A number closer to 0 indicates that the component should be positioned closer to the left or top side, respectively. A perfect 0.5 indicates that the component should be placed at the center, while a number nearing 1 indicates that the component should be positioned closer to the right or bottom. Currently, the only layout managers that use these properties are the BoxLayout and OverlayLayout managers; all AWT 1.1 layout managers ignore these properties and position their children by other means. We discuss these managers further in Chapter 11.

3.5.7 Adding Borders

It's easy to add borders to Swing components, a feature AWT lacks. The JComponent border property accepts objects that implement the javax.swing.border.Border interface. Figure 3-7 shows a component with a border.

Figure 3-7. Simple borders in Swing
figs/swng2.0307.gif

Swing currently provides several styles of borders, including an empty border. Each one extends the javax.swing.border.Border interface. In addition, you can surround a Swing component with multiple borders through the use of the CompoundBorder class. This class allows you to combine any two borders into a single border by specifying an outer and inner border. Because CompoundBorder accepts other compound borders, you can recursively layer as many borders as you like into a single border.

Using borders is extremely easy. For example, one of Swing's border styles is an etched border. Here is how you might create a border similar to the one in Figure 3-7:

JLabel label = new JLabel("A Simple Label"); label.setBorder(BorderFactory.createEtchedBorder( ));

One important characteristic of Swing is that if a border property is set on a component, the border overrides the component's insets property. Swing allows the programmer to specify an empty border, so you can still pad the component with extra space as well as provide a border if you use a CompoundBorder. If the border property is null, the default insets are used for the component instead. Borders are covered in more detail in Chapter 13.

3.5.8 Working with Tooltips

JComponent also provides Swing components with support for tooltips. Tooltips are small windows of text that pop up when the user rests the mouse over the target component. They typically supplement the meaning of an icon or button, but they can also provide the user with instructions or important information about the underlying component. The tooltip usually disappears after a designated amount of time (four seconds by default) or if the mouse is moved outside of the component's bounds.

Simple string-based tooltips can be automatically set or retrieved using the toolTipText property of JComponent, as shown here:

JButton button = new JButton("Press Me!"); // JButton extends JComponent. button.setToolTipText("Go Ahead!"); System.out.println(button.getToolTipText( ));

Figure 3-8 shows what a tooltip looks like on the screen.

Figure 3-8. A tooltip for a component
figs/swng2.0308.gif

JComponent does not manage tooltips by itself; it gets help from the ToolTipManager class. The ToolTipManager continually scans for mouse events on components that have tooltips. When the mouse passes into a component with a tooltip set, the ToolTipManager begins a timer. If the mouse has not left the component's region in 0.75 seconds, a tooltip is drawn at a preset location near the component. If the mouse has moved out of a region for longer than 0.5 seconds, the tooltip is removed from the screen.

With the default setToolTipText( ) and getToolTipText( ) methods, JComponent handles the creation of an appropriate tooltip. If you want to get more creative, however, Swing provides a separate object for tooltips: JToolTip. With it, you can completely redefine the characteristics of a tooltip by declaring your own JToolTip object and overriding the createToolTip( ) method of JComponent to return it to the ToolTipManager on demand.

We cover the JToolTip object and the ToolTipManager in more detail in Chapter 27.

3.5.9 Client Properties

Swing components can maintain a special table of properties called "client properties." This provides specialized properties that can be meaningful in components only in certain instances. For example, let's assume that a specific L&F uses a client property to store information about how a component should display itself when that L&F is activated. As you might guess, this client property would be meaningless when another L&F is activated. Using the client properties approach allows various L&Fs to expand their component properties without deluging the Swing source base with L&F-specific data.

The name "client properties" is somewhat confusing because client properties are distinct from JavaBeans-style properties. Obviously, there's a big difference: unlike JavaBeans properties, you can create new client properties without subclassing; you can even create new client properties at runtime. These two methods in JComponent store and retrieve client properties:

myComponent.putClientProperty("aClientProperty", Boolean.TRUE); Boolean result = (Boolean)getClientProperty("aClientProperty");

Because we are using a hashtable, the properties must be objects and not primitive data types; we must use the Boolean object instead of simply setting true or false.

3.5.10 Double Buffering

The JComponent class allows all Swing components to take advantage of double buffering. The idea behind double buffering is that it takes longer for a component to render its individual parts on screen than it does for a rectangular-area copy to take place. If the former occurs over multiple screen refreshes, the human eye is likely to catch the component in the process of being drawn, and it may appear to flicker. With the latter, the screen is usually updated as fast as the monitor can refresh itself.[2]

[2] Area copies are always faster because they are performed by the operating system or even the graphics card of the computer. At this level, they are commonly referred to as "bit-block transfers," or BitBLTs.

When double buffering is activated in Swing, all component rendering performed by the repaint manager is done in an offscreen buffer. Upon completion, the contents of the offscreen buffer are quickly copied (not redrawn) on the screen at the component's position. You can request double buffering for a particular component by accessing the boolean doubleBuffered property of JComponent. Passing in true to the setDoubleBuffered( ) method enables double buffering; false shuts it off:

JButton button = new JButton("Test Button"); button.setDoubleBuffered(true);    // Turns on double buffering

You can use the isDoubleBuffered( ) method to check if double buffering is currently enabled on a Swing component. The component level setting is only a request, and Swing double buffering may be completely disabled at the level of the repaint manager (for example, when running under an operating system like Mac OS X, double buffering is always performed by the window manager, so doing it again in Swing would simply throw away processor cycles for no benefit). See Section 28.4.2 for more details and for information about how you can use graphics-accelerated "volatile images" in SDK 1.4 to further speed up Swing double buffering.

With double buffering, transparency is maintained in nonopaque components because the graphics underneath the component are copied into the buffer before any offscreen rendering takes place. However, there is a slight penalty for double buffering nonopaque components because Swing performs two area copies instead of one: one to copy the context in which the component is drawn to the offscreen buffer before drawing, and one to copy this context plus the rendered component back to the screen.

Buffers also chew up a great deal of memory, so the repaint manager tries to avoid using more than one offscreen buffer at a time. For example, if an offscreen buffer has been set for both a container and one of its children, the buffer for the parent container is used for both components.

3.5.11 Serialization

Objects that extend JComponent are serializable; that is, the object's data at any point can be written out, or serialized, onto an output stream, which might send it over a network or save it in a file.[3] The serialized output can later be deserialized back into memory, where the object continues to operate from its original state. Object serialization gives Java programmers a powerful and convenient way to store and retrieve object data, as opposed to saving or transmitting state data in custom-made storage files. Serialization also provides the ability to transfer active components quickly from one virtual machine to another, which can be useful in remote method invocation (RMI) and other forms of distributed computing.

[3] The only exceptions to this are fields marked with the transient keyword.

You can serialize components in Swing as you normally would in Java by passing a reference to the object into the writeObject( ) method of an ObjectOutputStream object. In the event that the serialized object contains a reference to another object, the serialization algorithm recursively calls writeObject( ) on that object as well, continuing until all objects in the class hierarchy are serialized. The resulting object graph is then written out to the output stream. Conversely, you can deserialize a component back in by using the readObject( ) method of an ObjectInputStream, which reverses the entire process.

Serialization in its current form is suited primarily for short-term uses such as RMI and interprocess communication. The binary file produced by serialization is guaranteed to be readable only by another virtual machine of the same revision. If you want to store components for long-term (archival) use, you can use the XMLEncoder to dump the public properties (as defined by the JavaBeans spec) to an XML file. See the java.beans.XMLEncoder class for more details.

3.5.12 The DebugGraphics Class

Lightweight components are rendered entirely in Java, as opposed to offloading their work to a native heavyweight peer. The abstract Graphics class outlines platform-independent implementations for line-drawing, image-painting, and area-copying and filling that a lightweight peer can call upon to draw itself. If you create your own component, or extend an existing one, a Graphics object is often passed to the UI delegate's paint( ) method to help with the drawing.

Sometimes the way you intend a component to be painted, however, isn't how it appears on the screen. Debugging painting problems can prove to be troublesome, especially when dealing with transparency, opaqueness, and double buffering. JComponent, however, can generate a special version of the Graphics object, called DebugGraphics, which it can pass to a UI delegate's paint( ) method. This object can take a set of user-configurable debugging options that modify how a component is drawn to the screen.

If you wish to activate debugging for the component's graphics, you can pass one or more debugging flags (see Table 3-7) into JComponent's setDebugGraphicsOptions( ) method.

Table 3-7. Constants for DebugGraphics options

DebugGraphics constant

Description

DebugGraphics.FLASH_OPTION

Causes each graphics primitive to flash a configurable number of times as it is being rendered.

DebugGraphics.LOG_OPTION

Prints a text message to the screen as each graphics primitive is drawn.

DebugGraphics.BUFFERED_OPTION

Raises a window that shows the drawing that is taking place in the offscreen buffer. This is useful in the event that the double-buffered feature has been activated.

DebugGraphics.NONE_OPTION

Disables all debug graphics options.

The debug options outlined in Table 3-7 are bits in a binary mask; you can set more than one at the same time by using the bitwise OR ( | ) operator, as shown here:

JButton myButton = new JButton("Hello");  // JButton extends JComponent. myButton.setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION                                        | DebugGraphics.LOG_OPTION);

When any of the debug graphics options are set, the getComponentGraphics( ) method of JComponent returns a DebugGraphics object instead of a normal Graphics object. As we mentioned earlier, the same type of object is passed to the UI delegate of the component. When a component draws itself, it calls upon the functionality of the DebugGraphics object to perform the task, just as it would with a typical Graphics object. The drawing primitives are then slowed or logged so that the user can help identify any problems.

3.5.13 Focus and Focus Cycle Methods

The term focus refers to the active component on the screen. We typically think of the active component as the frame or window that is the current recipient of mouse and keyboard events. Other components, such as buttons and text fields, can have the focus as well. Visual cues, like a colored title bar or a dashed outline, often help us determine where the current focus resides.

When we click on another component with the mouse, the focus is typically shifted, and that component is now responsible for consuming mouse and keyboard events. You can also traverse the focus by pressing the Tab key to move forward or the Tab and the Shift key together to move backward. This causes the focus to cycle from one component to the next, eventually completing a loop and returning to its original position. This loop is called the focus cycle.

A group of components within a single container can define a focus cycle of its own. If the container has its own focus cycle, the focus repeatedly traverses through all of its children that accept the focus. The focus cycle is typically determined by the location of components in the container, although you can create your own focus traversal policy if you require different behavior. With the default focus policy, the component closest to the top-left corner of the container always receives focus first. The focus then moves from left to right across the components, and from top to bottom. Figure 3-9 shows how the default focus cycle shifts focus between components in a container.

Figure 3-9. The default container focus traversal policy
figs/swng2.0309.gif

If a container has a focus cycle of its own, it should override the Container method isFocusCycleRoot( ) and return true. If the method returns true, then the container is known as the root container of the focus cycle.

With SDK 1.2 or 1.3, you can explicitly name the component that should receive the focus after a given JComponent by setting its nextFocusableComponent property. In addition, focus can be programmatically requested through the JComponent method requestFocus( ), which the focus manager can call to shift the focus to this component. This is often done when the user selects the object (i.e., presses a JButton). If you don't want your component to be able to respond to requestFocus( ) calls, you can set the requestFocusEnabled property of JComponent to false.

With SDK 1.4, this method of managing focus was replaced by the more flexible FocusTraversalPolicy class as part of a major overhaul of the whole focus system. This class allows you to define a focus policy to manage a container. (In this case, "focus policy" simply means an algorithm to figure out which component follows, and which one precedes, the current component in the focus cycle.) One advantage of moving to policy-based management is that generic policies can be developed for containers no more need to hook up individual components.

There is an important distinction here: setting the requestFocusEnabled property to false does not mean that the focus cannot be traversed onto your component; it simply means that it cannot be programmatically requested. JComponent provides a similar property, focusable,[4] that you can enable or disable to specify whether a component ever receives focus at all.

[4] Prior to SDK 1.4, the focusTraversable property (now depricated) was used instead; setting this property to false allowed the component to receive focus programmatically but not through traversal.

We discuss the concept of focus in detail in Chapter 28.

3.5.14 Keyboard Events

Swing components can be programmed to trigger various actions when certain keystrokes occur. For example, components automatically handle focus-related keyboard events. The default focus mechanism watches for Tab and Shift-Tab keystrokes, adjusting the focus and consuming the keystrokes. If the focus mechanism does not know how to handle a keystroke, and no registered low-level KeyListeners have consumed it, JComponent checks to see whether the processComponentKeyEvent( ) method consumes it. The default implementation does nothing, the idea being that you can override it in a subclass if you want to react to a keystroke in your own way. You're not likely to want to use that approach, though, because it's much less flexible than what happens next: if nothing has consumed the key event, JComponent checks to see if a keyboard action has been registered for that keystroke. A set of maps provide a convenient way to translate key events to appropriate component-related actions.

Translation to an action starts by converting the key event to the KeyStroke that represents it. This is used as a key to check the component's InputMap for a corresponding action name (the InputMap could return any kind of object, but convention dictates that it be a String corresponding to a logical action name). The result of this lookup, if not null, is used in turn as a key to look in the component's ActionMap for an Action to perform. Assuming that a non-null Action was found, its actionPerformed method is invoked (as described in Section 3.1.2 earlier in this chapter).

It might seem like overkill to use a two-map lookup like this. Wouldn't it be simpler to just put the Actions directly in the InputMap? It turns out there are a couple of good reasons for the second layer. Although a given type of component generally supports a well-defined set of logical operations, the specific Action classes that implement them often vary depending on the L&F in use. Similarly, the keys that are used to invoke the actions vary between L&Fs, which leads to a complex coupling between the component and the L&F-specific UI delegate. Separating the two concepts into two maps provides an easy-to-understand translation between KeyStrokes and logical event names, and from event names to specific Action implementations. It also means that InputMaps are nicely self-documenting; it's easy to turn them into a human-readable table that shows the functions assigned to various keys.[5]

[5] For more details about the design goals of this key-binding mechanism, which was introduced in SDK 1.3, see http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html, which also describes the limitations of the previous mechanism. If you are still working with a pre-1.3 version of Swing, you can find the first edition's version of this section, which discusses how to use the old key-binding mechanism, on this book's web site, http://www.oreilly.com/catalog/jswing2/.

InputMaps and ActionMaps are also designed to be easy to share between components (or even similar component types). They have a parent property that is checked if a binding isn't found, so common functionality can be placed in a shared parent map, and component-specific definitions added to a local map on an as-needed basis; the text components make extensive use of this capability. JComponent makes this easy by providing newly initialized components with empty InputMaps and ActionMaps whose parents are the (likely shared) map provided by the UI. So, as a developer, you never need to worry about the possible existence of shared maps; you can just start adding your custom mappings and rely on Swing to provide the rest.

Before showing you the details of how to register keyboard actions, there is one more complication to clarify. The process outlined here described a single InputMap used to translate keystrokes to action names. In fact, components have three separate InputMaps to address the fact that there are different situations under which a component might be asked to respond to a keyboard event. The most obvious case, which probably sprang to mind, is when the component itself is the owner of the keyboard focus. Components can also have a chance to respond to key events if they don't have focus in two other cases. First, a component may respond if it is an ancestor of (contains) the focused component. Think of a ScrollPane, in which the Page Up and Page Down keys remain functional even though you're working with the contents of the pane rather than the pane itself. Second, a component may respond if it is simply inside a window that is focused (this is how button mnemonics work). In order to create the proper InputMap, the methods to manipulate them offer a condition parameter whose legal values are shown in Table 3-8.

Table 3-8. Constants for InputMap selection

Constant

Description

WHEN_FOCUSED

The InputMap used when the component has the focus

WHEN_IN_FOCUSED_WINDOW

The InputMap used when the component resides in a container that has the focus

WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

The InputMap used when the component is the ancestor of (contains) the component that currently has the focus

You obtain a component's input map through one of the following two methods (these were mentioned in Table 3-6, but bear repeating in this context):

public InputMap getInputMap(int condition)

Return the input map to be used under the specified condition.

public InputMap getInputMap( )

A convenience method that calls getInputMap(WHEN_FOCUSED), which is the most commonly used condition.

Looking up the action map is simpler since there's only one method:

public ActionMap getActionMap( )

Return the action map associated with the component.

A brief example illustrates how to perform the common task of assigning an event to a component using this binding mechanism. Suppose we wanted to extend the example program in Section 3.1.3.5 to perform a download whenever the F8 key is pressed. One way we could do this is by adding the following lines to the end of the ActionExample constructor:

exampleButon.getActionMap( ).put("download", exampleAction); exampleButton.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(   KeyStroke.getKeyStroke("F8"), "download");

The first line binds the logical action name download to our sample download action within the button's action map. The second line causes the F8 key to trigger this logical action whenever the button's window has the focus, even if the button itself does not. This two-step registration process in which both an InputMap and the ActionMap are retrieved and modified is very common when working with custom actions because of the two-stage, key-mapping process. If you're simply changing or adding bindings for a standard keystroke or action, you need to work with only one of the maps.

To remove a binding you've set, both types of maps provide a remove method that takes a KeyStroke object that will be removed from the mapping. The clear method removes all mappings. Neither of these methods affect inherited mappings. In fact, if you added a keystroke that overrode an inherited mapping, removing that keystroke restores the inherited mapping. If you actually want to block an inherited mapping without providing a new action, register a mapping to the action "none", which convention mandates never has an Action bound to it.

There are corresponding methods for setting the map properties themselves, of course. These are used far less commonly, but do provide a way to eliminate the inherited parent maps provided by the L&F's UI delegate:

public void setInputMap(int condition)
public void setActionMap(ActionMap actionMap)

Replace the corresponding map completely, eliminating any inherited mappings. Passing a null argument causes the component to have no bindings at all.

Note that if you replace the mappings this way, there's no way to get back the previously inherited mappings unless you keep a reference to the original maps yourself. (See Appendix B for a list of default bindings.)

3.5.15 Accessibility

As we mentioned in Chapter 1, Swing components support accessibility options. Accessibility options are constructed for users who have trouble with traditional user interfaces and include support for alternative input and output devices and actions. There are several parts to accessibility (covered in detail in Chapter 25). JComponent implements the methods required by the Accessible interface, though it does not implement the interface itself.

The accessibleContext property holds an AccessibleContext object that is the focal point of communication between the component and auxiliary accessibility tools. There's a different default context for each kind of JComponent. For more information, see Chapter 25.

3.5.16 Events

Table 3-9 shows the events fired by JComponent (not counting the many events it inherits from the AWT classes).

Table 3-9. JComponent events

Event

Description

PropertyChangeEvent

A change has occurred in JComponent.

VetoablePropertyChangeEvent

A change has occurred in JComponent that can be vetoed by interested listeners.

AncestorEvent

An ancestor of a JComponent has moved or changed its visible state.

3.5.16.1 Event methods

The 1.3 SDK added an access method for general event listeners:

public EventListener[] getListeners(Class listenerType)

This method pulls listeners from a protected listListener field based on the specified type. All the various addListener( ) methods for JComponent add their listeners to this list. Subclasses can add their own listeners to the listListener field. See Chapter 27 for more information on event listener lists.

The following methods may move to java.awt.Component in the future:

public void firePropertyChange(String propertyName, byte oldValue, byte newValue)
public void firePropertyChange(String propertyName, char oldValue, char newValue)
public void firePropertyChange(String propertyName, short oldValue, short newValue)
public void firePropertyChange(String propertyName, int oldValue, int newValue)
public void firePropertyChange(String propertyName, long oldValue, long newValue)
public void firePropertyChange(String propertyName, float oldValue, float newValue)
public void firePropertyChange(String propertyName, double oldValue, double newValue)
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue)

Fire a PropertyChangeEvent to all registered listeners if newValue differs from oldValue. There are overloaded versions of this method for each primitive data type.

public void addPropertyChangeListener(PropertyChangeListener listener)
public void removePropertyChangeListener(PropertyChangeListener listener)

Add or remove a PropertyChangeListener to the event registration list.

public void addVetoableChangeListener(VetoableChangeListener listener)
public void removeVetoableChangeListener(VetoableChangeListener listener)

Add or remove a VetoableChangeListener to the event registration list. A VetoableChangeListener is allowed to veto any property changes that occur inside a component. If only one veto occurs, the property is not changed.

public void addAncestorListener(AncestorListener listener)
public void removeAncestorListener(AncestorListener listener)

Add or remove an AncestorListener to the event registration list. All registered objects are notified if any of the components' ancestors change position or are made visible or invisible.

JComponent also inherits all the event listener registration methods from its AWT superclasses, Container and Component. From Component, it inherits the methods to add or remove a ComponentListener, FocusListener, KeyListener, MouseListener, or MouseMotionListener. From Container, it inherits the methods to add or remove a ContainerListener. We won't describe all the listener interfaces here; for more information, see Java AWT Reference by John Zukowski (O'Reilly). However, you should note that Swing supports only the event model established in JDK 1.1. To receive an event, you must always register as a listener with the JComponent that generates the event events are never propagated through the containment hierarchy, as they were in JDK 1.0.

3.5.17 Constructor

public JComponent( )

Initialize a simple JComponent and set the layout manager to null.

3.5.18 Graphics Methods

protected Graphics getComponentGraphics(Graphics g)

Accept a graphics context and modify its foreground color and font to match the current defaults. If the debug graphics option has been activated, the method returns a special graphics object that the programmer can configure for debugging component drawing with the color and font modifications.

public void update(Graphics g)

Equivalent to paint(g). This is significantly different from the update( ) method of Component, which first cleared the component's background. In Swing, clearing the component is handled by ComponentUI, based on whether the component is opaque.

public boolean contains(int x, int y)

Return true if the coordinates passed in are inside the bounding box of the component, false otherwise. The method always asks the UI delegate first, giving it an opportunity to define the bounding box as it sees fit. If the UI delegate does not exist for this component, or cannot define the bounding box, the standard component contains( ) method is invoked.

public Insets getInsets (Insets insets)

Copy the JComponent's insets into the given Insets object and return a reference to this object.

public void paint(Graphics g)

The primary method that the AWT subsystem calls upon for components to draw themselves if they are not obscured. This method delegates most of its work to the protected methods paintComponent( ), paintBorder( ), and paintChildren( ), which it calls in that order. Because this method performs its own internal calculations, it is generally not a good idea to override it in a subclass; if you want to redefine how a component draws itself, override paintComponent( ) instead.

public void reshape(int x, int y, int w, int h)

Reset the bounds property of the component.

protected void paintComponent(Graphics g)

Draw the component using the graphics context provided. Unless overridden, it simply turns around and calls the paint( ) method of the delegate. If there is no delegate, the method does nothing.

protected void paintChildren(Graphics g)

Cycle through each of the component's children, invoking the paint( ) method on each one.

protected void paintBorder(Graphics g)

Paint the border (or borders) outlined by the border property of JComponent. Note that if a border is defined, JComponent ignores its own insets and uses the border instead.

public void repaint(long tm, int x, int y, int width, int height)
public void repaint(Rectangle r)

Place a request to repaint the specified region on the repaint manager's update queue. The initial variable tm of the first repaint( ) method is no longer used and can be ignored. Because the redrawing queue knows the correct order to draw various component layers, it is widely preferred that you call these methods, instead of directly invoking paint( ).

public void paintImmediately(int x, int y, int w, int h)
public void paintImmediately(Rectangle r)

Force an immediate repaint of the specified region in the component. This method is invoked by the repaint manager when it is time for the component to draw itself; the programmer should not call this method. This method may move to java.awt.Component in the future.

public void revalidate( )

Add the current component to the repaint manager's revalidation queue, which is located on the system event queue.

public void computeVisibleRect(Rectangle visibleRect)

Calculate a Rectangle that represents the intersection of the component's own visible rectangle and each of its ancestors. The result is placed in the visibleRect property and is used to determine how much of a component is drawn on the screen.

3.5.19 Focus Methods

public void requestFocus( )

Shift the focus to this component if the requestFocusEnabled property is true.

public boolean requestDefaultFocus( )

Shift the focus to a default component, typically the first focus-traversable component in the current container. If the method is unable to find such a component, it returns false. This method was deprecated in SDK 1.4. (You should generally move your focus-related code to FocusTraversalPolicy implementations.)

public void grabFocus( )

Used by focus managers to shift the focus to this component, regardless of the state of the requestFocusEnabled property. Because of this, it is generally better to use requestFocus( ) instead of this method.

public boolean hasFocus( )

Return true if this component currently has the focus. This method is defined in java.awt.Component in JDK 1.2.

3.5.20 Tooltip Methods

public String getToolTipText(MouseEvent event)

Retrieve the text used for the component's tooltip, given the appropriate mouse event. JComponent always returns the current toolTipText property. However, you can override this method in your own component if you want to return different strings based on various mouse events.

public Point getToolTipLocation(MouseEvent event)

This method currently returns null. You can override it in your own component to specify the local component coordinates where its tooltip should be displayed. If the method returns null, Swing chooses a location for you.

public JToolTip createToolTip( )

Return a new instance of JToolTip by default. If you want to extend the JToolTip class with a tooltip of your own, you can override this method in your components, forcing it to return the new class to the tooltip manager.

3.5.21 Client Properties Methods

public final Object getClientProperty(Object key)

Search the client property list for the Object specified under the appropriate key. It returns null if no object is found.

public final void putClientProperty(Object key, Object value)

Insert the specified client property value under the appropriate key. If the value passed in is null, the property is cleared from the list.

3.5.22 Miscellaneous Methods

protected void setUI(ComponentUI u)

Install u as the UI delegate for the component, effectively changing the component's L&F. This change doesn't appear onscreen until updateUI( ) is called.

public void updateUI( )

Called by the current UIManager to notify the component that its L&F has changed, and that the UI delegate should repaint itself.

public void scrollRectToVisible(Rectangle aRect)

Call similar methods up the component hierarchy. You can override this method at any level if you want to explicitly handle scrolling updates.

public static boolean isLightweightComponent(Component c)

A convenience method that returns a boolean indicating whether the component passed is a lightweight component. If it is, the method returns true. Otherwise, it returns false. This method may move to java.awt.Component in the future.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net