I l@ve RuBoard Previous Section Next Section

26.3 Key Look-and-Feel Classes and Interfaces

In this section, we'll take an in-depth look at several key classes and interfaces that make up the Swing PLAF design. Figure 26-4 shows the relationships between the classes (and interfaces) we will examine in this section.

Figure 26-4. High-level L&F class diagram
figs/swng2.2604.gif

Before we look at the details of each of these classes, we'll quickly describe the role each one plays:

LookAndFeel

The abstract base class from which all the different L&Fs extend. It defines a number of static convenience methods, as well as some abstract methods required by every L&F.

UIDefaults

An L&F is responsible for defining a set of default properties. UIDefaults is a Hashtable subclass that holds these properties. The properties include UIClassID to ComponentUI subclass mappings (e.g., "TreeUI" to MetalTreeUI) as well as lower-level defaults, such as colors and fonts.

UIDefaults.ActiveValue and UIDefaults.LazyValue

These inner interfaces of UIDefaults enable some optimizations for resource values.

UIResource

This is an empty interface (like Serializable or Cloneable) used to tag property values. It allows values defined by the L&F to be distinguished from values set by the user, as described in Section 26.3.5 later in this chapter.

UIManager

If you've ever changed the L&F of a Swing program at runtime, you're probably already familiar with this class. UIManager is responsible for tracking a global view of the L&Fs available in an application. It keeps track of the currently installed L&F and provides a mechanism to change the L&F. All of its methods are static, but it does provide a mechanism that allows multiple applets within a single virtual machine to use different L&Fs.

UIManager.LookAndFeelInfo

This inner class is used to describe available L&Fs without actually having to load the L&F classes. UIManager uses this class to provide a list of available L&Fs.

ComponentUI

This is the base class common to all UI delegates. It defines all of the methods related to painting and sizing that the different delegate subclasses must implement.

JComponent

You're certainly familiar with this class by now—it's the base class for all of the Swing components. We include it in this diagram to show that at any time, each JComponent has a reference to a single ComponentUI. ComponentUI objects may, however, be shared by multiple components. JComponent was covered in gory detail back in Chapter 3.

The next several sections explore the details of each of these key players. After that, we'll be ready to look at a variety of ways that you can customize the L&F of your applications.

26.3.1 The LookAndFeel Class

The LookAndFeel class is the abstract base class from which all L&F implementations are derived. Extensions of this class are responsible for defining everything that's unique to a particular L&F. Figure 26-5 shows the inheritance hierarchy for the most common Swing L&F classes.

Figure 26-5. Swing LookAndFeel class diagram
figs/swng2.2605.gif

Each L&F is defined in its own package.[2] This allows the numerous UI delegate classes and other support classes that make up the L&F to be conveniently grouped. The abstract BasicLookAndFeel class is the where the Swing L&F framework really lives. All of the abstractions for the various component UI classes are defined here. By extending the classes in the javax.swing.plaf.basic package, the other L&F packages (including ones you might create yourself) are relieved from having to worry about a lot of the details of the various component UI classes and can focus on rendering the components in a particular style.

[2] Note that the Windows and Motif L&Fs remain in the com.sun.java.swing.plaf package hierarchy.

Another special class in this diagram is the MultiLookAndFeel class, which allows you to use multiple L&Fs at the same time. This is used to provide accessibility support, perhaps by providing an L&F that speaks instead of drawing to the screen. Another possible use is automated UI testing. We'll get into the javax.swing.plaf.multi package later in the chapter.

26.3.1.1 Properties

The LookAndFeel class defines the properties shown in Table 26-1. As you can see, none of these properties have useful values defined in this base class (those listed as abstract have accessors that are abstract). The defaults property should be defined by each L&F to contain the UI delegate class defaults, default system colors and fonts, etc. (We'll get into more detail on how UIDefaults is used later in the chapter.) The description , ID, and name properties just describe the L&F. Table 26-2 shows the values of these properties for the most common Swing L&Fs to give you an idea of how they are used.

Table 26-1. LookAndFeel properties

Property

Data type

get

is

set

Default value

defaults

UIDefaults

·

   

null

description

String

·

   

Abstract

ID

String

·

   

Abstract

name

String

·

   

Abstract

nativeLookAndFeel

boolean

 

·

 

Abstract

supportedLookAndFeel

boolean

 

·

 

Abstract

supportsWindowDecorations1.4

boolean

·

   

false

1.4since 1.4

Table 26-2. Property values for common Swing L&Fs

Look-and-feel

ID

Name

Description

MacLookAndFeel

Mac

MacOS

The MacOS L&F

MetalLookAndFeel

Metal

Metal

The Java L&F

MotifLookAndFeel

Motif

CDE/Motif

The CDE/Motif L&F

WindowsLookAndFeel

Windows

Windows

The Microsoft Windows L&F

The nativeLookAndFeel property indicates whether the L&F is designed to emulate the native L&F of the platform on which the application is currently running. For example, if you are running on a Windows machine, the WindowsLookAndFeel returns true if you invoke isNativeLookAndFeel( ). Similarly, on a Solaris machine, the MotifLookAndFeel returns true while the MacLookAndFeel returns true on a Macintosh.

The supportedLookAndFeel property indicates whether the L&F is allowed to be used on the current platform. This was put in place primarily for legal reasons[3]—certain L&Fs may not be executed on platforms other than the native platform even if they're part of the standard Java distribution.

[3] Specifically, this was added to keep the WindowsLookAndFeel from being used on other platforms. The MacLookAndFeel is simply absent on non-Macintosh Java distributions.

Finally, the supportsWindowDecorations property indicates whether the L&F can provide undecorated RootPaneUI instances. Such L&Fs support client-drawn window decorations (border, title bar, and controls for manipulating the window), allowing the L&F to take over the task of drawing these items from the underlying peer window.

26.3.1.2 Constructor
public LookAndFeel( )

The default constructor is available, so subclasses do not need to explicitly call one.

26.3.1.3 Methods

In addition to the accessors for the properties we listed earlier, the LookAndFeel base class defines the following public methods:

public void initialize( )

Perform any L&F-specific initialization needed, other than setting defaults. It is called by the UIManager just before invoking the getDefaults( ) method for the first time. The default implementation does nothing.

public void provideErrorFeedback(Component component)

Invoked when the user tries to do something that's not allowed, such as pasting into an uneditable JTextField that has focus. The default implementation just beeps; subclasses can override this and provide additional feedback.

public String toString

Return a string representation of the L&F, built from the description property and the L&F class name.

public void uninitialize( )

Used to perform cleanup before the L&F is replaced. This typically happens when a new L&F is chosen to replace the current one.

26.3.1.4 Static convenience methods

The following static methods are defined in this class. They are used by L&F subclasses to simplify common tasks.

public static Object getDesktopPropertyValue(String systemPropertyName, Object fallbackValue)

Return the specified system desktop property by calling the default Toolkit's getDesktopProperty( ) method. If this method is null, fallbackValue is returned. Available since 1.4, this method provides convenient access to AWT's desktop property information. Desktop properties are platform-specific settings that track systemwide desktop themes that might be changed by the user during program execution. Because the properties vary by platform and may not exist at all on some platforms, few cross-platform programs use them.

public static void installBorder(JComponent c, String defaultBorderName)

Install a default border for the component if there is currently no border defined or if the current border is tagged as a UIResource. The border to install is retrieved from the UIManager using defaultBorderName as a key.

public static void installColors(JComponent c, String defaultBgName, String defaultFgName)

Install default foreground and background colors for the component if they are not currently defined or if the current colors are tagged as UIResources. The colors to install are retrieved from the UIManager, using defaultBgName and defaultFgName as keys.

public static void installColorsAndFont(JComponent c, String defaultBgName, String defaultFgName, String defaultFontName)

Call installColors( ) and install a default font using the same rules with respect to UIResources. A call to this method typically looks something like:

LookAndFeel.installColorsAndFont(theLabel, "Label.background",
  "Label.foreground", "Label.font");
public static Object makeIcon(Class baseClass, String gifFile)

Create a UIResource from the specified filename that can be entered into a defaults table. The filename should be relative to the directory containing the L&F classes so that it can be loaded by calling baseClass.getResourceAsStream( ). The returned object is a UIDefaults.LazyValue, meaning that the GIF is loaded only if it is needed.

public static void uninstallBorder(JComponent c)

Remove a default border from the component if the current border is tagged as a UIResource.

There are also a number of static methods for setting up key bindings that map KeyStrokes typed by the user to Actions that should be taken by components. The method makeKeyBindings has been available since Swing was introduced. A significantly improved keyboard-binding infrastructure was introduced with Version 1.3, which uses the InputMap and ActionMap classes to unify the two previous incompatible APIs. Three methods related to this new API were added to the LookAndFeel class: loadKeyBindings, makeComponentInputMap, and makeInputMap. The full details of the change in key-binding infrastructure (and the reasons behind it) are discussed in the Swing Connection article "Keyboard Bindings in Swing" at http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html.

In all of these methods, the specification of the keystrokes to associate with an action can either be KeyStroke objects or Strings describing a KeyStroke. In the latter case, an attempt is made to convert the string to a KeyStroke. This string may begin with any number of modifiers taken from the following list: shift, control, meta, alt, button1, button2, button3. The modifiers (if any) should be followed by the name of the key (the names defined in KeyEvent, without the VK_ at the beginning). For example, x would simply map to new KeyStroke(0, KeyEvent.VK_X) while Ctrl-Alt-Delete would map to:

new KeyStroke(InputEvent.CTRL_MASK|InputEvent.ALT_MASK, KeyEvent.VK_DELETE)

26.3.2 The UIDefaults Class

One of the key things that distinguishes an L&F is a set of default properties. These properties are stored in an extension of java.util.Hashtable called UIDefaults.

Any type of value that is important to an L&F can be stored in this table. The most common ones are fonts, colors, borders, icons, and, most importantly, UI-delegate class names. We'll get into the details of populating a UIDefaults object for a custom L&F in Section 26.7 later in this chapter.

Starting with SDK 1.4, the UIDefaults class supports localized values for its defaults through new methods (and variants of existing methods) that add a Locale parameter.

26.3.2.1 Events

UIDefaults fires a PropertyChangeEvent any time a default is added to the table. If an individual property is added, the name of the property is the default's key. If a bulk addition is made, the property name is "UIDefaults", and both the old and new property values reported are null.

The following event-related methods are defined:

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

Define who receives notification when changes are made to the defaults.

public PropertyChangeListener[] getPropertyChangeListeners( )

Return an array, which may be empty, containing all the current (added and not yet removed) PropertyChangeListeners registered for UIDefaults. Available since SDK 1.4.

26.3.2.2 Constructors
UIDefaults( )

This is the default constructor; it creates an empty hashtable to hold the defaults for the L&F.

UIDefaults(Object[] keyValueList)

This constructor accepts an array of objects and creates a defaults table initialized with the specified key/value pairs. The keyValueList array must be of even length, containing alternating keys and values. For example:

{ "textFont", new Font("Serif", Font.PLAIN, 12), "backgroundColor", Color.black }
26.3.2.3 Methods

If you are not creating your own L&F, you don't need to be concerned with the methods defined in UIDefaults. UIManager provides a set of static methods that perform the same functions on the UIDefaults object associated with the currently installed L&F:

public Object get(Object key)

Like its Hashtable equivalent, this method returns the object associated with the given key. The method is overridden to provide special handling of two special types of values: ActiveValue and LazyValue. (These interfaces are described at the end of this section.) Values that are not of one of these special types are returned as is.

public Boolean getBoolean(Object key)
public Border getBorder(Object key)
public Color getColor(Object key)
public Dimension getDimension(Object key)
public Font getFont(Object key)
public Icon getIcon(Object key)
public Insets getInsets(Object key)
pulic int getInt(Object key)
public String getString(Object key)

Provide type-safe access to certain types of properties. They return the object associated with the given key, attempting to cast it to the appropriate return type. If the cast is unsuccessful, the methods return null.

As of SDK 1.4, all of the preceding get methods search the resource bundles associated with the UIDefaults using its default locale if the requested default is not present in the Hashtable. Each method also now has a variant with a second parameter of type Locale to allow lookup of a default for a specific locale.

public void setDefaultLocale(Locale l)
public Locale getDefaultLocale( )

These methods determine (or examine) the default Locale established for the UIDefaults, and thus affect the behavior of the single-parameter versions of the default-lookup methods. Available since Version 1.4.

public void addRes ourceBundle(String bundleName)
public void removeResourceBundle(String bundleName)

Determine the list of resource bundles that are searched for localized values. When looking up the localized value for a default, the bundles are searched in the reverse order they were added (the most recent is searched first). Also since Version 1.4.

Defaults stored in resource bundles don't support ActiveValue or LazyValue.

public ComponentUI getUI(JComponent target)

Return the UI-delegate object associated with the current L&F for the target component that was passed in. Locates the correct object by invoking the getUIClassID( ) method of the input component to obtain the correct name of the delegate, cross-referencing it against the class defaults table using the getUIClass( ) method, and invoking the UI delegate's static createUI( ) method to obtain an appropriate UI instance. If the method is unsuccessful, it invokes the getUIError( ) method.

public Class getUIClass(String uiClassID)
public Class getUIClass(String uiClassID, ClassLoader uiClassLoader)

These methods search the defaults table for the given class ID. The value from the table should be the class name of the appropriate UI delegate. After retrieving the class name, the name itself is used as a key into the table in an attempt to find the corresponding Class object. If the class is not already in the table, it is loaded by calling uiClassLoader.loadClass( ) or by calling Class.forName( ) if uiClassLoader is null. Once the class is found, it is added to the table (with its name as the key) so that subsequent calls to getUIClass( ) can get the class directly from the table. Note that the first version of this method calls the second with a null class loader.

public Object put(Object key, Object value)

Add the given key/value pair to the table. If the key already exists in the table, its value is replaced. If a new key is added, or if an existing key is assigned a new value, a PropertyChangeEvent is fired to all registered listeners. The property name sent with this event is the key. If the value is null, the key is removed from the table, and the PropertyChangeEvent is fired with a new value of null.

public void putDefaults(Object[] keyValueList)

Transfer all of the key/value pairs in the object array to the table, firing a single PropertyChangeEvent on completion. The property name in this event is "UIDefaults". (See the UIDefaults(Object[]) constructor for a description of the expected format of the input array.) Existing properties whose names are absent from the array are not affected by this method. If a null value is associated with a key in the array, the corresponding property is removed.

protected void getUIError(String msg)

Called if the getUI( ) method fails to successfully retrieve a UI delegate for its target component. By default, it just dumps a stack trace and continues. If you want to add specific functionality to handle errors, you can override this method in a subclass.

26.3.3 The UIDefaults.ActiveValue Interface

As we mentioned in the previous section, the UIDefaults.get( ) method handles two types of values differently. The first of these, ActiveValue, is used for properties that potentially need to be instantiated each time they are queried. Such properties typically include renderers and editors.

26.3.3.1 Method

The interface defines one method:

public abstract Object createValue(UIDefaults table)

Implementations of this method are expected to return a new instance of some class. When UIDefaults.get( ) finds an ActiveValue, it invokes this method and returns the resulting object, rather than returning the ActiveValue itself.

26.3.3.2 Creating an ActiveValue

Implementations of this interface are typically very simple. For example:

Object myActiveValue  = new UIDefaults.ActiveValue( ) {
  public Object createValue(UIDefaults table) {
    return new MyThing( );
  }
};

26.3.4 The UIDefaults.LazyValue Interface

The other special type of value recognized by UIDefaults.get( ) is the LazyValue. This interface allows properties to be lazily instantiated when they are asked for, avoiding the creation of values that may not be used. This is typically used for objects that take a long time to create, such as icons, or objects that are likely to be unused.

26.3.4.1 Method

The interface defines the following method:

public abstract Object createValue(UIDefaults table)

Implementations of this method are expected to return a new instance of some class. When UIDefaults.get( ) finds a LazyValue, it invokes this method and replaces the value in the table with the resulting object. (See Figure 26-6.)

Figure 26-6. Conversion of a LazyValue
figs/swng2.2606.gif
26.3.4.2 Creating a LazyValue

Prior to SDK 1.3, the typical code for a LazyValue was just like the code we presented for ActiveValue. The only difference was the intent. With ActiveValue, createValue( ) is called every time a key is accessed. With LazyValue, createValue( ) is called only once because the LazyValue object is replaced with the real value returned by createValue( ).

Unfortunately, performance benchmarking revealed that the savings of deferring the execution of the createValue methods was overwhelmed by the cost of class loading all the anonymous LazyValue implementations! To fix this, a new class was introduced. UIDefaults.ProxyLazyValue implements the LazyValue interface and uses reflection to create its proxied instance when asked to do so. Reflection allows the class-loading process to be deferred until the default is actually needed, thus finally achieving the performance gain that has always been the goal of the interface. When implementing your own L&F defaults, you should use the ProxyLazyValue class instead of creating new anonymous classes of the LazyValue interface.

Figure 26-7 shows how LazyValues and ActiveValues are handled differently by UIDefaults. Pay special attention to the differences in handling the second get( ) call.

Figure 26-7. UIDefaults's handling of LazyValue and ActiveValue
figs/swng2.2607.gif

26.3.5 The UIResource Interface

UIResource is an empty interface (similar in concept to java.io.Serializable or java.lang.Cloneable) used to tag L&F resource values that have been set by the L&F. The purpose of tagging these values is to allow Swing to distinguish between values set by the user and values set by the L&F. Consider the following scenario in which the L&F is changed during execution:

  • User creates a JFoo component

  • L&F installs a default foreground property with the value Color.blue

  • User calls setForeground(Color.orange) on the new JFoo object

  • User (or the application logic) changes the L&F

  • New L&F installs its default foreground property with the value Color.red

Without the concept of UIResources, changing the L&F would cause all of the resource values (such as the foreground color in the above scenario) to be reset when the L&F is changed, since each L&F installs its defaults as part of its initialization process. Using UIResource to tag L&F-installed resource values allows Swing to make an instanceof UIResource check before installing the default values for new resources. If this operation returns false (indicating that the user changed the resource to something other than the default), the resource is not replaced by the new L&F. In our example, the foreground color would remain orange even after changing the L&F.

Figure 26-8 shows the various implementations of this interface, used when representing different types of resource values. The resource classes that extend existing classes (ActionMapUIResource, ComponentInputMapUIResource, ColorUIResource, DimensionUIResource, FontUIResource, InputMapUIResource, and InsetsUIResource) provide nothing more than constructors that simply pass their arguments up to the superclass. In the BorderUIResource and IconUIResource classes, a delegate object, which is the "real" resource, is held. Methods called on the resource object are just forwarded to the actual border or icon.[4]

[4] This delegation approach is necessary because Border and Icon are interfaces, not classes.

Figure 26-8. UIResource class diagram
figs/swng2.2608.gif
26.3.5.1 Static BorderUIResource methods

In addition to the delegation behavior described earlier, the BorderUIResource class defines four static methods used to access single instances of four common Border types tagged as UIResources. The methods are listed here for your convenience:

public static Border getEtchedBorderUIResource( )
public static Border getLoweredBevelBorderUIResource( )
public static Border getRaisedBevelBorderUIResource( )
public static Border getBlackLineBorderUIResource( )

This is similar to the role of the BorderFactory class, except that fewer borders are maintained, and they are specifically managed as BorderUIResources.

26.3.5.2 BorderUIResource inner classes

BorderUIResource defines static inner classes that subclass most[5] of the border classes defined in the javax.swing.border package.

[5] For whatever reason, there is no SoftBevelBorderUIResource defined.

Each of these classes provides a set of constructors that map directly to the constructors provided by the real Border classes. See Chapter 13 for a list of these constructors.

public static class BevelBorderUIResource extends BevelBorder implements UIResource
public static class CompoundBorderUIResource extends CompoundBorder implements UIResource
public static class EmptyBorderUIResource extends EmptyBorder implements UIResource
public static class EtchedBorderUIResource extends EtchedBorderUI implements UIResource
public static class LineBorderUIResource extends LineBorder implements UIResource
public static class MatteBorderUIResource extends MatteBorder implements UIResource
public static class TitledBorderUIResource extends TitledBorder implements UIResource

26.3.6 The UIManager Class

UIManager provides a simple interface to a variety of information about the current L&F, as well as to any other available L&Fs. All of its methods are static, so you never need to instantiate a UIManager; you simply call its static methods. In this section, we'll look at a variety of ways that you can control the L&F of your applications using the UIManager.

The most common use of UIManager is to set the current L&F. If you don't want to use the L&F Swing selects by default, you simply call one of the setLookAndFeel( ) methods during initialization. For example:

public static void main(String[] args) {
 try {
  UIManager.setLookAndFeel(new com.sun.java.swing.plaf.motif.MotifLookAndFeel( ));
 } catch (UnsupportedLookAndFeelException e) {System.err.println("Bummer!");}
}

As discussed in Section 26.1 earlier in this chapter, think carefully before forcing a specific L&F on users of your application, and consider letting them overrule your choice.

26.3.6.1 LAFState

One issue that comes up whenever you have a static class is how to manage the fields of the class in a browser-based environment. It's important that one applet doesn't affect another by changing the value of some static field. In the case of the UIManager, it's important to ensure that the L&F used in one applet doesn't affect any other applets.

UIManager gets around this by storing much of the state in a private inner class called LAFState. Each application context has an associated LAFState, so there is no risk of an applet changing the L&F of another applet from some other source. This strategy is completely transparent to Swing developers.

26.3.6.2 UIManager look-and-feel concepts

The UIManager tracks a number of different L&F properties. These include:

Current L&F

The most important L&F tracked by the UIManager is the L&F currently in use. The default value depends on the platform; it is usually the cross-platform L&F or (less often) the system L&F.

Cross-platform L&F

This L&F is not modeled after a preexisting native L&F. By default, this is Swing's Metal L&F.

System L&F

This L&F is designed to emulate the native L&F of the platform the application is currently running on. The value of this L&F (unsurprisingly) depends on the platform on which the program is running. The default implementation of UIManager checks the value of the os.name system property. If this contains the string Windows, the Windows L&F is used; the presence of Solaris or SunOS causes the Motif L&F to be used, and the string Mac results in an attempt to use the (nonexistent) com.sun.java.swing.plaf.mac.MacLookAndFeel. Any other platform uses the cross-platform L&F. Of course, you shouldn't put too much stock in the default implementation; this is an obvious candidate for adjustment when porting Swing to a new platform. For example, in the shipping Mac OS X implementation, UIManager returns the correct string: com.apple.mrj.swing.MacLookAndFeel, as shown in Figure 26-5.

Installed L&Fs

This is the set of L&Fs currently available to the application, which depends on the platform.

Auxiliary L&Fs

This (possibly empty) set of L&Fs is used to provide accessibility support from a Swing application. They exist in conjunction with the current L&F to provide some sort of nongraphical representation of the display. A typical auxiliary L&F might be called AudioLookAndFeel. There are no auxiliary L&Fs by default.

The following example shows how you can verify the L&Fs defined by your system:

// UIManagerDefaults.java
//
import javax.swing.*;

public class UIManagerDefaults {
  public static void main(String[] args) {
    System.out.println("Default look-and-feel:");
    System.out.println("  " + UIManager.getLookAndFeel( ).getName( ));

    UIManager.LookAndFeelInfo[] inst = UIManager.getInstalledLookAndFeels( );
    System.out.println("Installed look-and-feels: ");
    for (int i=0;i<inst.length;i++) {
      System.out.println("  " + inst[i].getName( ));
    }

    LookAndFeel[] aux = UIManager.getAuxiliaryLookAndFeels( );
    System.out.println("Auxiliary look-and-feels: ");
    if (aux != null) {
      for (int i=0;i<aux.length;i++) {
        System.out.println("  " + aux[i].getName( ));
      }
    }
    else {System.out.println("  <NONE>");}

    System.out.println("Cross-Platform:");
    System.out.println("  " + UIManager.getCrossPlatformLookAndFeelClassName( ));

    System.out.println("System:");
    System.out.println("  " + UIManager.getSystemLookAndFeelClassName( ));

    System.exit(0);
  }
}

Running this program on a Windows machine produces the following output:

Default look-and-feel:
  Metal Look and Feel
Installed look-and-feels:
  Metal
  CDE/Motif
  Windows
Auxiliary look-and-feels:
  <NONE>
Cross-Platform:
  javax.swing.plaf.metal.MetalLookAndFeel
System:
  com.sun.java.swing.plaf.windows.WindowsLookAndFeel

By contrast, running it on a Macintosh produces the following:[6]

[6] Note in particular the different default L&F, as discussed at the beginning of this chapter.

Default look-and-feel:
  MacOS
Installed look-and-feels: 
  Metal
  MacOS Adaptive
  CDE/Motif
  Windows
Auxiliary look-and-feels: 
  <NONE>
Cross-Platform:
  javax.swing.plaf.metal.MetalLookAndFeel
System:
  com.apple.mrj.swing.MacLookAndFeel
26.3.6.3 Look-and-feel properties file

In the previous section, we examined the default values for various L&F properties. These defaults are hardcoded inside the UIManager version used by a given platform. Since all the useful methods in this class are static, there's no opportunity for you to change these defaults by extending UIManager. Instead, any changes to be made to these defaults must be made in the Swing properties file. During initialization, the UIManager looks for a file called swing.properties in the lib directory under the Java home directory defined by the java.home system property. (This is the mechanism Apple uses to make the Mac L&F the default on Mac OS X.)

Table 26-3 shows the various properties that you can define in this file, along with a description of their purpose. Default values for these properties vary, as described in the preceding section.

Table 26-3. L&F options in Swing.properties

Property name

Purpose

swing.defaultlaf

Class name of the default L&F class

swing.auxiliarylaf

Comma-delimited list of auxiliary L&F class names

swing.plaf.multiplexinglaf

Class name of the multiplexing L&F class

swing.installedlafs

Comma-delimited list of installed L&F keys (used below)

swing.installedlaf.*.name

For each installed L&F, contains the L&F name indexed by the key used in swing.installedlafs

swing.installedlaf.*.class

For each installed L&F, contains the class name indexed by the key used in swing.installedlafs

The first three properties listed here must contain fully qualified class names. The swing.auxiliarylaf property may contain a comma-separated list of class names. The swing.installedlafs property should contain a comma-separated list of arbitrary L&F identifiers. Each identifier listed here must also have a name and class property defined for it.

Here's a hypothetical swing.properties file in which we explicitly set values for all of the possible properties:

swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel
swing.auxiliarylaf=audio.AudioLookAndFeel,braille.BrailleLookAndFeel
swing.plaf.multiplexinglaf=supermulti.SuperMultiLookAndFeel
swing.installedlafs=motif,metal,audio,braille
swing.installedlaf.motif.name=CDE/Motif
swing.installedlaf.motif.class=com.sun.java.swing.plaf.motif.MotifLookAndFeel
swing.installedlaf.metal.name=Metal
swing.installedlaf.metal.class=javax.swing.plaf.metal.MetalLookAndFeel
swing.installedlaf.audio.name=Audio
swing.installedlaf.audio.class=audio.AudioLookAndFeel
swing.installedlaf.braille.name=Braille
swing.installedlaf.braille.class=braille.BrailleLookAndFeel

These same property values may also be defined on the command line. For example, to start your Java application using the Motif L&F, all you have to do is enter this at the command line:

% java -Dswing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel MyClass
26.3.6.4 Events

The UIManager class fires a PropertyChangeEvent any time the active L&F is changed. The property name in the event is lookAndFeel.

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

Add or remove a PropertyChangeListener from the listener list.

26.3.6.5 UIDefaults convenience methods

UIManager defines a number of methods that simplify access to properties defined in the UIDefaults table of the currently installed L&F. These methods do nothing more than obtain the current UIDefaults object and call its corresponding method. It provides static versions of all the get methods listed in Section 26.3.2 earlier in this chapter, including the variants that specify a Locale.

26.3.6.6 Other static methods
public static void addAuxiliaryLookAndFeel(LookAndFeel laf)

Add an auxiliary L&F. See Section 26.4 later in this chapter for more information.

public static LookAndFeel[] getAuxiliaryLookAndFeels( )

Return an array of objects representing the currently installed auxiliary L&Fs.

public static String getCrossPlatformLookAndFeelClassName( )

Return the name of the L&F class that is the default cross-platform L&F. This is hardcoded to return the Metal L&F class name.

public static UIDefaults getDefaults( )

Return the UIDefaults object associated with the current L&F, incorporating any defaults you have defined. See Section 26.3.6.8 below for more information.

public static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels( )

Return an array of UIManager.LookAndFeelInfo objects that identify the L&Fs currently installed on the system. This information is useful if you want to present the user with the names of the available L&Fs without going through the time-consuming process of actually instantiating all the L&F objects.

public static LookAndFeel getLookAndFeel( )

Retrieve the currently active L&F.

public static UIDefaults getLookAndFeelDefaults( )

Return the UIDefaults table defined for the current L&F, ignoring any defaults you have defined. See Section 26.3.6.8 for more information.

public static String getSystemLookAndFeelClassName( )

Return the name of the L&F class native to the current platform.

public static ComponentUI getUI(JComponent target)

Return the appropriate UI delegate for the component passed in. The delegate is instantiated from the current L&F.

public static void installLookAndFeel(String name, String className)

Create a UIManager.LookAndFeelInfo entry from the two string parameters and add it to the installedLookAndFeels property.

public static void installLookAndFeel(UIManager.LookAndFeelInfo info)

Add the given UIManager.LookAndFeelInfo object to the installedLookAndFeels property.

public static boolean removeAuxiliaryLookAndFeel(LookAndFeel laf)

Remove an auxiliary L&F. Returns true if the L&F was found and removed.

public static void setInstalledLookAndFeels(UIManager.LookAndFeelInfo infos[]) throws SecurityException

Reset the list of currently installed L&Fs to those specified in the array infos.

26.3.6.7 Changing the look-and-feel

The following two methods change the L&F currently in use. If you call one of these methods before creating any Swing components (at the beginning of your main( ) or init( ) method, perhaps), you don't need to do anything else.

However, if you have already created any Swing components, you need to notify each of them that the L&F has changed so that they can get their new UI delegates. To do so, call the updateUI( ) method on every component in your application. Fortunately, Swing provides a method called SwingUtilities.updateComponentTree( ) that makes all of the updateUI( ) calls for you. All you have to do is pass in each of your top-level containers (typically JFrames, JWindows, and JApplets) one at a time. The SwingUtilities class, which is covered in detail in Chapter 27, takes care of updating the UI for each component contained in the component you pass in.

public static void setLookAndFeel(LookAndFeel newLookAndFeel) throws UnsupportedLookAndFeelException

Reset the current L&F. If the given L&F is supported, the old L&F, if there is one, is uninitialized, and the new one is initialized. A property change event is fired to any registered listeners, indicating that the L&F for the system has been changed. This method also calls getDefaults( ) on the new L&F to set up the UI defaults.

public static void setLookAndFeel(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException

Set the current L&F, given the fully qualified name of a class that extends LookAndFeel. It attempts to turn the given class name into a LookAndFeel object and, if it is successful, passes this object to the other setLookAndFeel( ) method. Note the additional exceptions thrown by this method. These exceptions are actually thrown by the Class.forName( ) and Class.newInstance( ) methods, which are used to turn the given String into a LookAndFeel object.

26.3.6.8 Managing defaults

The UIManager tracks three levels of defaults, as shown in Figure 26-9.

Figure 26-9. UIManager default management
figs/swng2.2609.gif

As you know, each L&F defines a set of default values stored in a UIDefaults object. But this is only the beginning of the story. You are free to modify these default values in your application. If you do, the UIManager tracks these modifications in a separate UIDefaults object, which is hidden from you. When you ask for the current defaults by calling getDefaults( ), or when you make a get( ) call to retrieve a specific key, you are actually accessing a sequence of UIDefaults objects. The values you have explicitly set have the highest priority, those set by the L&F are considered next, and those defined as system properties are considered only if there is no user- or L&F-defined property.[7]

[7] Currently, UIManager defines only a single system default called "FocusManagerClassName". This is used by the FocusManager class to determine which FocusManager implementation to use. In the future, additional system defaults may be defined. Note that the new focus mechanism introduced in SDK 1.4 (and described in Section 28.1) made this default obsolete by deprecating FocusManager. The value is still present, however.

Any defaults that your program explicitly sets remain defined, even if you change the L&F. If you want access to the defaults defined by the L&F, ignoring those you have overridden or added, you can call getLookAndFeelDefaults( ).

26.3.7 The UIManager.LookAndFeelInfo Class

This simple inner class is used to provide information about an L&F without having to load the L&F class itself. The UIManager provides a list of the available L&Fs by providing instances of these objects. The data stored in them can be used, for example, to populate a list of user-selectable L&Fs.

26.3.7.1 Properties

The properties listed in Table 26-4 make up the heart of this simple class. The name property contains a short name that describes the L&F, and the className property contains the fully qualified class name of the corresponding LookAndFeel implementation.

Table 26-4. UIManager.LookAndFeelInfo properties

Property

Data type

get

is

set

Default value

className

String

·

   

From constructor

name

String

·

   

From constructor

26.3.7.2 Constructor
public LookAndFeelInfo(String name, String className)

Set the object's two properties.

26.3.7.3 Method

This is the only method other than the accessors for the two properties:

public String toString( )

Return a string built from the object's properties.

26.3.8 The ComponentUI Class

The abstract ComponentUI class, found in the com.sun.java.swing.plaf package, is the abstract class from which all UI delegates must inherit. It defines the core set of methods called by the JComponent base class, which all UI delegates must implement. Very few of the Swing components invoke component-specific methods on their UI objects, so the methods defined here form the core of the communication from the component classes to the UI classes.

In the Swing L&Fs, the individual delegate class names are typically formed by joining the L&F name, the component name (minus the J), and UI. For example, the Metal UI-delegate class for JSlider components is called MetalSliderUI. This naming convention is not enforced in any way, but it's a nice, simple standard that's well worth following.

26.3.8.1 Methods

Here are the methods defined in this abstract class. Note that they all take a JComponent. As we discussed in the chapter introduction, this means that the UI delegate does not need to keep a reference to the component and that the same delegate may be used for multiple components if desired.

public boolean contains( JComponent c, int x, int y)

Return a boolean indicating whether the specified component currently encompasses the specified point x, y. The default implementation simply delegates the call to the component by calling c.inside(x, y).

public int getAccessibleChildrenCount( JComponent c)

Return the number of accessible children in this object. This is delegated to the UI because a delegate might wish to represent different parts of itself as independently accessible components that don't map directly to Swing components. However, this default implementation just uses SwingUtilities.getAccessibleChildrenCount( ).

public Accessible getAccessibleChild( JComponent c, int i)

Return the accessible child at the specified index. The default implementation uses SwingUtilities.getAccessibleChild( ).

public Dimension getMaximumSize( JComponent c)

Return the maximum size for the given component. The default implementation returns getPreferredSize( ).

public Dimension getMinimumSize( JComponent c)

Should return the minimum size for the given component. The default implementation returns getPreferredSize( ).

public Dimension getPreferredSize( JComponent c)

Should return the preferred size for the given component. The default implementation returns null. If JComponent gets a null back from this method (or either of the two size methods listed previously), it defers the size query to its superclass (Container).

public void installUI( JComponent c)

Called just after the UI delegate is instantiated. The default implementation does nothing. Typically, this method adds listeners to the component, so the UI can update itself when the component changes state. It is also used to set various default values, such as fonts and colors.

public void paint(Graphics g, JComponent c)

Responsible for painting the component based on the component's current state. Since the implementation of this method is entirely component-specific, the default implementation does nothing. This method is called by the update( ) method, which is described later. It is not called directly by JComponent.

public void uninstallUI( JComponent c)

Called as the current L&F is being removed. The default implementation does nothing. Subclasses typically use this method to release any resources they may have created and, most importantly, remove any listeners they added in installUI( ).

public void update(Graphics g, JComponent c)

Called by JComponent.paintComponent( ). If you're familiar with the old AWT update/paint mechanism, this method should also be familiar. The key difference is that this version of update( ) fills the component's bounding rectangle with the background color only if the component is opaque. Whatever the opaqueness, this method finishes by calling paint( ).

26.3.8.2 Static method
public static ComponentUI createUI( JComponent c)

This static method, typically invoked by the UIManager, is used to obtain an instance of the component's UI delegate. This implementation actually throws an Error, since createUI( ) should not be called on the abstract ComponentUI class, and subclasses are expected to define a valid implementation. Concrete subclasses of ComponentUI should return an instance (either a new one or a shared one) of their specific class.

    I l@ve RuBoard Previous Section Next Section