I l@ve RuBoard |
![]() ![]() |
26.3 Key Look-and-Feel Classes and InterfacesIn 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![]() Before we look at the details of each of these classes, we'll quickly describe the role each one plays:
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 ClassThe 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![]() 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.
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 PropertiesThe 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.
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.
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
26.3.1.3 MethodsIn addition to the accessors for the properties we listed earlier, the LookAndFeel base class defines the following public methods:
26.3.1.4 Static convenience methodsThe following static methods are defined in this class. They are used by L&F subclasses to simplify common tasks.
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 ClassOne 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 EventsUIDefaults 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:
26.3.2.2 Constructors
26.3.2.3 MethodsIf 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:
26.3.3 The UIDefaults.ActiveValue InterfaceAs 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 MethodThe interface defines one method:
26.3.3.2 Creating an ActiveValueImplementations 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 InterfaceThe 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 MethodThe interface defines the following method:
Figure 26-6. Conversion of a LazyValue![]() 26.3.4.2 Creating a LazyValuePrior 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![]() 26.3.5 The UIResource InterfaceUIResource 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:
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]
Figure 26-8. UIResource class diagram![]() 26.3.5.1 Static BorderUIResource methodsIn 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:
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 classesBorderUIResource defines static inner classes that subclass most[5] of the border classes defined in the javax.swing.border package.
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.
26.3.6 The UIManager ClassUIManager 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!");} }
26.3.6.1 LAFStateOne 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 conceptsThe UIManager tracks a number of different L&F properties. These include:
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]
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 fileIn 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.
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 EventsThe UIManager class fires a PropertyChangeEvent any time the active L&F is changed. The property name in the event is lookAndFeel.
26.3.6.5 UIDefaults convenience methodsUIManager 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
26.3.6.7 Changing the look-and-feelThe 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.
26.3.6.8 Managing defaultsThe UIManager tracks three levels of defaults, as shown in Figure 26-9. Figure 26-9. UIManager default management![]() 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]
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 ClassThis 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 PropertiesThe 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.
26.3.7.2 Constructor
26.3.7.3 MethodThis is the only method other than the accessors for the two properties:
26.3.8 The ComponentUI ClassThe 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 MethodsHere 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.
26.3.8.2 Static method
![]() |
I l@ve RuBoard |
![]() ![]() |