New York University

Computer Science Department

Courant Institute of Mathematical Sciences

 

Session 3: Detailed Review of the Swing Packages

(http://developer.java.sun.com/developer/onlineTraining/GUI/Swing1/, http://java.sun.com/j2se/1.3/docs/guide/swing/,

http://developer.java.sun.com/developer/Books/swing2/)

 

 

Course Title: Extreme Java                                                    Course Number: g22.3033-007

Instructor: Jean-Claude Franchitti                                            Session: 3

 

 

I. What is in the Swing Packages?

Swing, like any well-behaved collection of Java software, groups its classes and interfaces into packages. Swing's packages, and their contents, are:

 

·       The Accessibility package: This package defines a contract between Java UI objects (such as screen readers, Braille terminals, and so on) and screen access products used by people with disabilities. Swing components fully support the accessibility interfaces defined in the accessibility package, making it easy to write programs with Swing that people with disabilities can use. The design of the accessibility package also makes it easy to add accessibility support to custom components. These custom components can extend existing Swing components, can extend existing AWT components, or can be lightweight components developed from scratch.

 

·       The Swing component package (javax.swingapi): The Swing component package is the largest of Swing's five packages. As Swing's initial beta release drew near, the Swing component package contained 99 classes and 23 interfaces. With a couple of exceptions, the Swing component package is also the package that implements all the component classes used in Swing. (The exceptions are JTableHeaderapi, implemented in the javax.swing.tableapi package, and JTextComponentapi, implemented in javax.swing.textapi. ) Swing's UI classes (those that have names beginning with "J") are classes that are actually used to implement components; all other Swing classes are non-UI classes that provide services and functionalities to applications and components. For more details, see the section headed "Varieties of Swing classes."

 

·       The basic package (javax.swing.plaf.basic): The basic package is the second largest package in the Swing set. It contains around 57 classes (but no interfaces). This package defines the default look-and-feel characteristics of Swing components. By subclassing the classes in the basic package, you can create components with your own customized pluggable L&F designs.)

 

·       The beaninfo package (javax.swing.beaninfo): Defines the SwingBeanInfo class, the superclass for all Swing BeanInfo classes. This package provides default implementations of the getIcon() and getDefaultPropertyIndex() methods, as well as utility methods like createPropertyDescriptor(), which makes it possible to write BeanInfo implementations. This class is meant to be used along with GenSwingBeanInfo(), a BeanInfo-class code generator.

 

·       The border package (javax.swing.borderapi): This package contains one interface and nine classes that you can subclass when you want to draw a specialized border around a component. (You don't have to touch this package when you create components with the default borders prescribed by whatever look and feel you are using. Swing draws default borders automatically around standard components.)

 

·       The event package (javax.swing.eventapi): The event package defines Swing-specific event classes. Its role is similar to that of the java.awt.event package.

 

Under most ordinary conditions, you'll probably never have to subclass the components provided in the event package. But if you ever need to create a component that needs to monitor some other particular component -- perhaps in a special way -- you can tap one of the classes provided in this package. Swing's JListapi class, for example, uses this package to determine when the user selects an item in a list.

 

·       The multi package (javax.swing.plaf.multi): The multi package contains Swing's Multiplexing UI classes (delegates), which permit components to have UIs provided by multiple user-interface (UI) factories.

 

·       The pluggable L&F package (javax.swing.plaf): The pluggable L&F package contains the classes that Swing uses to provide its components with the pluggable look-and-feel capabilities. Most developers will never need this package. However, if you're building a new look and feel and you can't take the conventional approach of just subclassing the classes in the swing.plaf.basic package, you can start with the abstract classes in swing.plaf.

 

·       L&F-specific packages that became available with the release of JFC 1.1 (Swing 1.0) include:

 

·       javax.swing.plaf.metal.

·       javax.swing.plaf.motif.

·       javax.swing.plaf.windows.

In addition, a MacOS for Macintosh computers is available separately. For details, see the Java Software Web site.

 

·       The table package (javax.swing.tableapi): The table package contains several low-level classes that Swing uses to help you build, view, and manipulate tables.

 

·       The text package (javax.swing.textapi): This package contains classes and interfaces that deal with components which contain text.

 

·       The HTML package (javax.swing.text.htmlapi): This package contains an HTMLEditorKitapi class that implements a simple text editor for HTML text files.

 

·       The RTF package (javax.swing.text.rtfapi): This package implements a simple text editor for RTF (rich text format) files.

·       The tree package (javax.swing.treeapi): The classes in this package are used to create, view, and manage tree components.

·       The undo package (javax.swing.undoapi): Developers of packages with undo capabilitie for example, text editors -- may need to know about classes and methods defined in this package.

 


Varieties of Swing classes

 

As noted previously, Swing's UI classes create components and controls that you can see on the screen, while Swing's non-UI classes provide vital services and functionalities to Swing's control classes and the applications that use them.

 

Some non-UI classes -- such as ButtonGroupapi and ImageIconapi -- are defined in the Swing component package, along with Swing's component classes. But most of Swing's non-component classes are implemented in the other packages listed in the section titled "What's in the Swing packages
" presented earlier this document.



The JComponent class

 

JComponentapi, an abstract class, is the root class of almost of of Swing's user-interface ("J") components. JComponent's root class is the Object class, which is defined in the java.lang.Object file. JComponent also inherits from the AWT Container and Component classes.

 

JComponent implements the Accessibilityapi and Serializable interfaces.

 

Swing's UI components inherit the following features and capabilities from the JComponent class:

 

·       Pluggable L&F : The JComponent class provides all JComponent-derived component classes with their "pluggable look-and-feel" technology, which allows the developer (or, optionally, the user) to specify or change the appearance and behavior of the Swing components used in an application, even while the program is being executed.

 

·       Extensibility : JComponent and its descendants have built-in technology that lets the developer combine and extend existing components to create new, customized components.

 

·       Smart trapping of keyboard events : JComponent provides its descendants with comprehensive keystroke-handling capabilities. Instead of trapping every keystroke and expecting you to discard the ones you aren't interested in, JComponent-derived classes can use methods implemented by the KeyStrokeapi class to trap just the keystrokes that you do care about, and can automatically initiate predetermined actions on specified components.

 

·       Customization of component borders : Any descendant of the JComponent class can display a customized border by calling the setBorder() method. JComponent-derived classes can call setBorder() to create both decorative borders and non-decorative borders (that is, borders that provide just margins and padding).

 

Look-and-feel implementations generally initialize a component's border property when the component is constructed. However, you can always override the initial value for the border property with your own custom border. The BorderFactory class provides a set of predefined borders that you can use.

 

·       Easy resizing of components : JComponent provides methods that make it easy for descendants to set the preferred, minimum, and maximum size for a component.

 

·       Tool Tips -- JComponent supports the use of tool tips -- short descriptions of components, or tips about how to use components, that pop up when the cursor lingers over a component.

·       Autoscrolling -- JComponent provides automatic scrolling in a list, table, or tree when the user is dragging the mouse.

 

·       Support for debugging : JComponent also provides its descendants with access to Swing's debugGraphicsapi mechanism, which displays the painting of components in slow motion so you can check to see whether components are being drawn properly.

 

·       Support for accessibility technology: This capability is provided by the Accessibilityapi interface.

 

·       Support for localization. JComponent provides technology for performing text I/O in languages other than English.


The JApplet class

 

JApplet is a new class used to create applets in Swing programs. Although JAppletapi is based on the old AWT Applet class, it is used a little differently. It provides input and painting mechanisms for its children -- something that the AWT Applet class did not do. It also provides other kinds of special support for applets with special requirements, such as applets that use the JLayeredPane class and the JMenuBar class.

 

One important characteristic of the JApplet class is that a JApplet object can have just one child: an object of a class named JRootPaneapi.

 

When you want to display a component inside an applet, you can't simply call the add() method to add the component to your applet; instead, must call a JRootPane method called getContentPane().


Swing Component Classes

 

Summary of Swing's component classes

 


Component

Description

Comments and
information sources

 

JAppletapi

Implements a Java applet.

The root class for applets.

JButtonapi

Implements a button component.

Swing's basic button class.

JCheckBoxapi

Implements a check-box component.

A class that creates and implements check boxes.

JColor-
Chooser

Displays and manages a color-chooser dialog.

In the swing.preview package.

JComboBoxapi

Implements a combo-box component.

Swing's version of the AWT Choice class. See JComboBoxapi.

JComponentapi

The mother of all Swing components.

Root class for most of Swing's UI classes. See JComponentapi.

JDesktopIconapi

Displays an iconified version of a JInternalFrameapi.

See JDesktopIconapi.

JDesktopPaneapi

Provides a pluggable DesktopManagerapiobject for
JInternalFrameapi objects.

See JDesktopPaneapi.

JDialogapi

Adds enhancements to java.awt.Dialog.

Contains a root pane as its only child; must be managed using content panes. See JDialogapi.

JfileChooser

Implements a file-chooser dialog box.

In the swing.preview package.

JFrameapi

Adds enhancements to java.awt.Frame.

To be covered in an upcoming issue; meanwhile,
see JFrameapi.

JInternal-
Frameapi

Implements a frame object that can be placed inside a JDesktopPaneapi object to emulate a native frame window.

To be covered in an upcoming issue; meanwhile, see JInternalFrameapi and JLayeredPaneapi.

JLabelapi

Creates a display area for displaying read-only text, an image, or both.

See JLabelapi.

JLayeredPaneapi

Can display multiple layered panes (JInternalFrameapi objects) inside a frame.

To be covered in an upcoming issue; meanwhile, see JInternalFrameapi and JLayeredPaneapi.

JListapi

Allows the user to select one or more objects from a list. A separate model, ListModelapi, represents the contents of the list.

See JListapi.

JMenuapi

Implements a menu component.

Descends from JComponentapi via JMenuItemapi.

JMenuBarapi

Implements a menu bar component.

Subclasses
JComponentapi.

JMenuItemapi

Implements a menu item component.

See JMenuItemapi.

JOptionPaneapi

Displays a dialog box that prompts the user for a choice and then passes that choice on to the executing program.

See JOptionPaneapi.

JPanelapi

Provides a generic container for organizing other components.

See JPanel api.

JPassword-
Fieldapi

Displays a field in which the user can type a password. The text of the password does not appear in the field as it is being typed.

See the JPassword-
Fieldapi
API.

JPopupMenuapi

Implements a popup menu.

Subclasses
JComponentapi
.

JProgressBarapi

Implements a progress-bar component.

See JProgressBarapi.

JRadioButtonapi

Implements a radio-button control.

A new class in Swing; subclasses JToggleButtonapi.

JRadioButton- MenuItemapi

Implements a radio-button menu item.

Subclasses
JMenuItemapi
.

JRootPaneapi

Instantiates in a single step an object made up of a glass pane, a layered pane, an optional menu bar, and a content pane.

See JRootPaneapi.

JScrollBarapi

Implements a scroll-bar object.

See JScrollBarapi.

JScrollPaneapi

Implements a scroll-pane object.

See JScrollPaneapi.

JSeparatorapi

Implements a menu separator object.

See JSeparatorapi.

JSliderapi

Implements a slider-bar object.

See JSliderapi.

JSplitPaneapi

Implements a split-pane component.

See JSplitPaneapi.

JTabbedPaneapi

Implements a tabbed-pane ("property-page") component.

See JTabbedPaneapi.

JTableapi

Implements a table component.

 See JTableapi.

JTextAreaapi

Implements a multiline area that can display editable or read-only text.

A subclass of JTextComponentapi, which subclasses JComponentapi.

JTextPaneapi

Implements a text component that can be marked up with attributes to be represented graphically.

See JTextPaneapi.

JToggle-
Buttonapi

Implements a two-stage button component.

Subclasses AbstractButtonapi.

JToolBarapi

Implements a dockable, floatable tool bar.

See JToolBarapi.

JToolTipapi

Implements a tool-tip component (a component that can display a short string, such as the name of components or a user tip)

See JToolTipapi.

JTreeapi

Displays a set of hierarchical data in a graphical outline format.

To be covered in an upcoming issue; meanwhile, see JTree.

JViewportapi

Provides a clipped view of an arbitrarily large component. Used by JScrollPaneapi.

See the JViewportapi.

JWindowapi

Adds enhancements to java.awt.Window.

See JWindowapi and JRootPaneapi.

 

 


II. Swing Design Goals

 

The overall goal for the Swing project was:

 

To build a set of extensible GUI components to enable developers to more rapidly develop powerful Java front ends for commercial applications.

 

To this end, the Swing team established a set of design goals early in the project that drove the resulting architecture. These guidelines mandated that Swing would:

 

1.    Be implemented entirely in Java to promote cross-platform consistency and easier maintenance.

 

2.    Provide a single API capable of supporting multiple look-and-feels so that developers and end-users would not be locked into a single look-and-feel.

 

3.    Enable the power of model-driven programming without requiring it in the highest-level API.

 

4.    Adhere to JavaBeansTM design principles to ensure that components behave well in IDEs and builder tools.

 

5.    Provide compatibility with AWT* APIs where there is overlapping, to leverage the AWT knowledge base and ease porting.


Swing's Roots in MVC

 

Swing architecture is rooted in the model-view-controller (MVC) design that dates back to SmallTalk. MVC architecture calls for a visual application to be broken up into three separate parts:

 

·       A model that represents the data for the application.

·       The view that is the visual representation of that data.

·       A controller that takes user input on the view and translates that to changes in the model.

 

Early on, MVC was a logical choice for Swing because it provided a basis for meeting the first three of our design goals within the bounds of the latter two.

 

The first Swing prototype followed a traditional MVC separation in which each component had a separate model object and delegated its look-and-feel implementation to separate view and controller objects.

 

The delegate

 

It was quickly discovered that this split didn't work well in practical terms because the view and controller parts of a component required a tight coupling (for example, it was very difficult to write a generic controller that didn't know specifics about the view). So the original designers collapsed these two entities into a single UI (user-interface) object.

 

Swing architecture is loosely based -- but not strictly based -- on the traditional MVC design. In the world of Swing, this new quasi-MVC design is sometimes referred to a separable model architecture.

 

Swing's separable model design treats the model part of a component as a separate element, just as the MVC design does. But Swing collapses the view and controller parts of each component into a single UI (user-interface) object.

 

To MVC or not to MVC?

 

One noteworthy point is that as an application developer, you should think of a component's view/controller responsibilities as being handled by the generic component class (such as. JButton, JTree, and so on). The component class then delegates the look-and-feel-specific aspects of those responsibilities to the UI object that is provided by the currently installed look-and-feel.

 

For example, the code that implements double-buffered painting is in Swing's JComponent class (the "mother" of most Swing component classes), while the code that renders a JButton's label is in the button's UI delegate class.

 

So Swing does have a strong MVC lineage. But it's also important to reiterate that the MVC architecture serves two distinct purposes:

 

·       First, separating the model definition from a component facilitates model-driven programming in Swing.

 

·       Second, the ability to delegate some of a component's view/controller responsibilities to separate look-and-feel objects provides the basis for Swing's pluggable look-and-feel architecture.

 

Although these two concepts are linked by the MVC design, they may be treated somewhat orthogonally from the developer's perspective. The remainder of this document will cover each of these mechanisms in greater detail.


Separable Model Architecture

 

It is generally considered good practice to center the architecture of an application around its data rather than around its user interface. To support this paradigm, Swing defines a separate model interface for each component that has a logical data or value abstraction. This separation provides programs with the option of plugging in their own model implementations for Swing components.

 

The following table shows the component-to-model mapping for Swing.

 

Component

Model Interface

Model Type

JButton

ButtonModel

GUI

JToggleButton

ButtonModel

GUI/data

JCheckBox

ButtonModel

GUI/data

JRadioButton

ButtonModel

GUI/data

JMenu

ButtonModel

GUI

JMenuItem

ButtonModel

GUI

JCheckBoxMenuItem

ButtonModel

GUI/data

JRadioButtonMenuItem

ButtonModel

GUI/data

JComboBox

ComboBoxModel

data

JProgressBar

BoundedRangeModel

GUI/data

JScrollBar

BoundedRangeModel

GUI/data

JSlider

BoundedRangeModel

GUI/data

JTabbedPane

SingleSelectionModel

GUI

JList

ListModel

data

JList

ListSelectionModel

GUI

JTable

TableModel

data

JTable

TableColumnModel

GUI

JTree

TreeModel

data

JTree

TreeSelectionModel

GUI

JEditorPane

Document

data

JTextPane

Document

data

JTextArea

Document

data

JTextField

Document

data

JPasswordField

Document

data



 


GUI-state vs. application-data models

 

The models provided by Swing fall into two general categories:

 

GUI-state models and application-data models.

 

GUI-state models

 

GUI state models are interfaces that define the visual status of a GUI control, such as whether a button is pressed or armed, or which items are selected in a list. GUI-state models typically are relevant only in the context of a graphical user interface (GUI).

 

While it is often useful to develop programs using GUI-state model separation -- particularly if multiple GUI controls are linked to a common state (such as in a shared whiteboard program), or if manipulating one control automatically changes the value of another -- the use of GUI-state models is not required by Swing. It is possible to manipulate the state of a GUI control through top-level methods on the component, without any direct interaction with the model at all. In the preceding table, GUI-state models in Swing are highlighted in blue.

 

Application-data models

 

An application-data model is an interface that represents some quantifiable data that has meaning primarily in the context of the application, such as the value of a cell in a table or the items displayed in a list. These data models provide a very powerful programming paradigm for Swing programs that need a clean separation between their application data/logic and their GUI. For truly data-centric Swing components, such as JTree and JTable, interaction with the data model is strongly recommended. Application-data models are highlighted in red in the table presented at the beginning of this section.

 

Of course with some components, the model categorization falls somewhere in between GUI state models and application-data models, depending on the context in which the model is used. This is the case with the BoundedRangeModel on JSlider or JProgressBar. These models are highlighted in purple in the preceding table.

 

Swing's separable model API makes no specific distinctions between GUI state models and application-data models; however, this difference has been clarified here to give developers a better understanding of when and why they might wish to program with the separable models.

 


Shared model definitions

 

Referring again to the table at the beginning of this section, notice that model definitions are shared across components in cases where the data abstraction for each component is similar enough to support a single interface without over-genericizing that interface.

 

Common models enable automatic connectability between component types. For example, because both JSlider and JScrollbar use the BoundedRangeModel interface, a single BoundedRangeModel instance could be plugged into both a JScrollbar and a JSlider and their visual state would always remain in sync.

 


The separable-model API

 

Swing components that define models support a JavaBeans bound property for the model. For example, JSlider uses the BoundedRangeModel interface for its model definition.

 

Consequently, it includes the following methods:

 

public BoundedRangeModel getModel()

public void setModel(BoundedRangeModelmodel)

 

All Swing components have one thing in common: If you don't set your own model, a default is created and installed internally in the component. The naming convention for these default model classes is to prepend the interface name with "Default." For JSlider, a DefaultBoundedRangeModel object is instantiated in its constructor:

 

public JSlider(int orientation, int min,

              int max, int value)

{

   checkOrientation(orientation);

   this.orientation = orientation;

   this.model =

      new DefaultBoundedRangeModel(value, 0, min, max);

   this.model.addChangeListener(changeListener);

   updateUI();

}

 

If a program subsequently calls setModel(), this default model is replaced, as in the following example:

 

JSlider slider = new JSlider();

BoundedRangeModel myModel =

   new DefaultBoundedRangeModel() {

      public void setValue(int n) {

        System.out.println("SetValue: "+ n);

        super.setValue(n);

      }

   });

slider.setModel(myModel);

 

For more complex models (such as those for JTable and JList), an abstract model implementation is also provided to enable developers to create their own models without starting from scratch. These classes are prepended with "Abstract".

 

For example, JList's model interface is ListModel, which provides both DefaultListModel and AbstractListModel classes to help the developer in building a list model.

 


Model change notification

 

Models must be able to notify any interested parties (such as views) when their data or value changes. Swing models use the JavaBeans Event model for the implementation of this notification. There are two approaches for this notification used in Swing:

 

·       Send a lightweight notification that the state has "changed" and require the listener to respond by sending a query back to the model to find out what has changed. The advantage of this approach is that a single event instance can be used for all notifications from a particular model -- which is highly desirable when the notifications tend to be high in frequency (such as when a JScrollBar is dragged).

 

·       Send a stateful notification that describes more precisely how the model has changed. This alternative requires a new event instance for each notification. It is desirable when a generic notification doesn't provide the listener with enough information to determine efficiently what has changed by querying the model (such as when a column of cells change value in a JTable).

 

Lightweight notification

 

The following models in Swing use the lightweight notification, which is based on the ChangeListener/ChangeEvent API:

 

Model

Listener

Event

BoundedRangeModel

ChangeListener

ChangeEvent

ButtonModel

ChangeListener

ChangeEvent

SingleSelectionModel

ChangeListener

ChangeEvent

 

The ChangeListener interface has a single generic method:

 

    public void stateChanged(ChangeEvent e)

 

The only state in a ChangeEvent is the event "source." Because the source is always the same across notifications, a single instance can be used for all notifications from a particular model. Models that use this mechanism support the following methods to add and remove ChangeListeners:

 

   public void addChangeListener(ChangeListener l)

   public void removeChangeListener(ChangeListener l)

 

Therefore, to be notified when the value of a JSlider has changed, the code might look like this:

 

JSlider slider = new JSlider();

BoundedRangeModel model = slider.getModel();        

model.addChangeListener(new ChangeListener() {

   public void stateChanged(ChangeEvent e) {

      // need to query the model

      // to get updated value...

      BoundedRangeModel m =

         (BoundedRangeModel)e.getSource();

      System.out.println("model changed: " +

          m.getValue());

   }

});

 

To provide convenience for programs that don't wish to deal with separate model objects, some Swing component classes also provide the ability to register ChangeListeners directly on the component (so the component can listen for changes on the model internally and then propagates those events to any listeners registered directly on the component). The only difference between these notifications is that for the model case, the event source is the model instance, while for the component case, the source is the component.

 

So the preceding example could be simplified to:

 

JSlider slider = new JSlider();

slider.addChangeListener(new ChangeListener() {

   public void stateChanged(ChangeEvent e) {

      // the source will be

      // the slider this time..

      JSlider s = (JSlider)e.getSource();

      System.out.println("value changed: " +

         s.getValue());

   }

});

 


Stateful notification

 

Models that support stateful notification provide event Listener interfaces and event objects specific to their purpose. The following table shows the breakdown for those models:

 

Model

Listener

Event

ListModel

ListDataListener

ListDataEvent

ListSelectionModel

ListSelectionListener

ListSelectionEvent

ComboBoxModel

ListDataListener

ListDataEvent

TreeModel

TreeModelListener

TreeModelEvent

TreeSelectionModel

TreeSelectionListener

TreeSelectionEvent

TableModel

TableModelListener

TableModelEvent

TableColumnModel

TableColumnModel-
Listener

TableColumnModel-
Event

Document

DocumentListener

DocumentEvent

Document

UndoableEditListener

UndoableEditEvent

 

The usage of these APIs is similar to the lightweight notification, except that the listener can query the event object directly to find out what has changed. For example, the following code dynamically tracks the selected item in a JList:

 

String items[] = {"One", "Two", "Three");

JList list = new JList(items);

ListSelectionModel sModel = list.getSelectionModel();

sModel.addListSelectionListener

       (new ListSelectionListener() {

   public void valueChanged(ListSelectionEvent e) {

      // get change information directly

      // from the event instance...

      if (!e.getValueIsAdjusting()) {

         System.out.println("selection changed: " +

           e.getFirstIndex());

      }

   }

});

 

Automatic View Updates

 

A model does not have any intrinsic knowledge of the view that represents it. (This requirement is critical to enable multiple views on the same model). Instead, a model has only a list of listeners interested in knowing when its state has changed. A Swing component takes responsibility for hooking up the appropriate model listener so that it can appropriately repaint itself as the model changes (if you find that a component is not updating automatically when the model changes, it is a bug!). This is true whether a default internal model is used or whether a program installs its own model implementation.

 


Ignoring models completely

 

As mentioned previously, most components provide the model-defined API directly in the component class so that the component can be manipulated without interacting with the model at all. This is considered perfectly acceptable programming practice (especially for the GUI-state models). For example, following is JSlider's implementation of getValue(), which internally delegates the method call to its model:

 

public int getValue() {

    return getModel().getValue();

}

 

And so programs can simply do the following:

 

JSlider slider = new JSlider();

int value = slider.getValue();

//what's a "model," anyway?

 


Separable model summary

 

So while it's useful to understand how Swing's model design works, it isn't necessary to use the model API for all aspects of Swing programming. You should carefully consider your application's individual needs and determine where the model API will enhance your code without introducing unnecessary complexity.

 

In particular, we recommend the usage of the Application-Data category of models for Swing (models for JTable, JTree, and the like) because they can greatly enhance the scalability and modularity of your application over the long run.

Swing's pluggable look-and-feel architecture allows us to provide a single component API without dictating a particular look-and-feel.

 

The Swing toolkit provides a default set of look-and-feels; however, the API is "open" -- a design that additionally allows developers to create new look-and-feel implementations by either extending an existing look-and-feel or creating one from scratch.

 

Although the pluggable look-and-feel API is extensible, it was intentionally designed at a level below the basic component API in such a way that a developer does not need to understand its intricate details to build Swing GUIs. (But if you want to know, read on . . .)

 

While we don't expect (or advise) the majority of developers to create new look-and-feel implementations, we realize PL&F is a very powerful feature for a subset of applications that want to create a unique identity. As it turns out, PL&F is also ideally suited for use in building GUIs that are accessible to users with disabilities, such as visually impaired users or users who cannot operate a mouse.

 

In a nutshell, pluggable look-and-feel design simply means that the portion of a component's implementation that deals with the presentation (the look) and event-handling (the feel) is delegated to a separate UI object supplied by the currently installed look-and-feel, which can be changed at runtime.

 


The pluggable look-and-feel API

 

The pluggable look-and-feel API includes:

 

·       Some small hooks in the Swing component classes.

·       Some top-level API for look-and-feel management.

·       A more complex API that actually implements look-and-feels in separate packages.

 

The component hooks

 

Each Swing component that has look-and-feel-specific behavior defines an abstract class in the swing.plaf package to represent its UI delegate. The naming convention for these classes is to take the class name for the component, remove the "J" prefix, and append "UI." For example, JButton defines its UI delegate with the plaf class ButtonUI.

 

The UI delegate is created in the component's constructor and is accessible as a JavaBeans bound property on the component. For example, JScrollBar provides the following methods to access its UI delegate:

 

   public ScrollBarUI getUI()

   public void setUI(ScrollBarUI ui)

 

This process of creating a UI delegate and setting it as the "UI" property for a component is essentially the "installation" of a component's look-and-feel.

 

Each component also provides a method which creates and sets a UI delegate for the "default" look-and-feel (this method is used by the constructor when doing the installation):

 

   public void updateUI()

 

A look-and-feel implementation provides concrete subclasses for each abstract plaf UI class. For example, the Windows look-and-feel defines WindowsButtonUI, a WindowsScrollBarUI, and so on. When a component installs its UI delegate, it must have a way to look up the appropriate concrete class name for the current default look-and-feel dynamically. This operation is performed using a hash table in which the key is defined programmatically by the getUIClassID() method in the component. The convention is to use the plaf abstract class name for these keys. For example, JScrollbar provides:

 

public String getUIClassID() {

   return "ScrollBarUI"; 

}

 

Consequently, the hash table in the Windows look-and-feel will provide an entry that maps "ScrollBarUI" to

"com.sun.java.swing.plaf.windows.WindowsScrollBarUI"

 

Look-and-feel management

 

Swing defines an abstract LookAndFeel class that represents all the information central to a look-and-feel implementation, such as its name, its description, whether it's a native look-and-feel -- and in particular, a hash table (known as the "Defaults Table") for storing default values for various look-and-feel attributes, such as colors and fonts.

 

Each look-and-feel implementation defines a subclass of LookAndFeel (for example, swing.plaf.motif. MotifLookAndFeel) to provide Swing with the necessary information to manage the look-and-feel.

 

The UIManager is the API through which components and programs access look-and-feel information (they should rarely, if ever, talk directly to a LookAndFeel instance). UIManager is responsible for keeping track of which LookAndFeel classes are available, which are installed, and which is currently the default.

 

The UIManager also manages access to the Defaults Table for the current look-and-feel.

 

The 'default' look and feel

 

The UIManager also provides methods for getting and setting the current default LookAndFeel:

 

   public static LookAndFeel

      getLookAndFeel()

 

   public static void

      setLookAndFeel(LookAndFeel newLookAndFeel)

     

   public static void

      setLookAndFeel(String className)

 

As a default look-and-feel, Swing initializes the cross-platform JavaTM look and feel (formerly known as "Metal"). However, if a Swing program wants to set the default Look-and-Feel explicitly, it can do that using the UIManager.setLookAndFeel() method. For example, the following code sample will set the default Look-and-Feel to be CDE/Motif:

 

UIManager.setLookAndFeel(

   "com.sun.java.swing.plaf.motif.MotifLookAndFeel");

 

Sometimes an application may not want to specify a particular look-and-feel, but instead wants to configure a look-and-feel in such a way that it dynamically matches whatever platform it happens to be running on (for instance, the. Windows look-and-feel if it is running on Windows NT, or CDE/Motif if it running on Solaris). Or, perhaps, an application might want to lock down the look-and-feel to the cross-platform Java look and feel.

 

The UIManager provides the following static methods to programmatically obtain the appropriate LookAndFeel class names for each of these cases:

 

  public static String

     getSystemLookAndFeelClassName()

  public static String

     getCrossPlatformLookAndFeelClassName()

 

So, to ensure that a program always runs in the platform's system look-and-feel, the code might look like this:

 

UIManager.setLookAndFeel(

   UIManager.getSystemLookAndFeelClassName());

 

Dynamically Changing the Default Look-and-Feel

 

When a Swing application programmatically sets the look-and-feel (as described above), the ideal place to do so is before any Swing components are instantiated. This is because the UIManager.setLookAndFeel() method makes a particular LookAndFeel the current default by loading and initializing that LookAndFeel instance, but it does not automatically cause any existing components to change their look-and-feel.

 

Remember that components initialize their UI delegate at construct time, therefore, if the current default changes after they are constructed, they will not automatically update their UIs accordingly. It is up to the program to implement this dynamic switching by traversing the containment hierarchy and updating the components individually. (NOTE: Swing provides the SwingUtilities.updateComponentTreeUI() method to assist with this process).

 

The look-and-feel of a component can be updated at any time to match the current default by invoking its updateUI() method, which uses the following static method on UIManager to get the appropriate UI delegate:

 

   public static ComponentUI getUI(JComponent c)

 

For example, the implementation of updateUI() for the JScrollBar looks like the following:

 

public void updateUI() {

   setUI((ScrollBarUI)UIManager.getUI(this));

}

 

And so if a program needs to change the look-and-feel of a GUI hierarchy after it was instantiated, the code might look like the following:

 

// GUI already instantiated, where myframe

// is top-level frame

try {

 

   UIManager.setLookAndFeel(

     "com.sun.java.swing.plaf.motif.MotifLookAndFeel");

   myframe.setCursor(

         Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

   SwingUtilities.updateComponentTreeUI(myframe);

   myframe.validate();

 

} catch (UnsupportedLookAndFeelException e) {

           

} finally {

   myframe.setCursor

         (Cursor.getPredefinedCursor

             (Cursor.DEFAULT_CURSOR));

}

 

Managing look-and-feel data

 

The UIManager defines a static class, named UIManager.LookAndFeelInfo, for storing the high-level name (such as. "Metal") and particular class name (such as "com.sun.java.swing.plaf.MetalLookAndFeel") for a LookAndFeel. It uses these classes internally to manage the known LookAndFeel objects. This information can be accessed from the UIManager via the following static methods:

 

   public static LookAndFeelInfo[]

      getInstalledLookAndFeels()

 

   public static void

      setInstalledLookAndFeels(LookAndFeelInfo[] infos)

          throws SecurityException

 

   public static void

      installLookAndFeel(LookAndFeelInfo info)

 

   public static void

      installLookAndFeel(String name, String className)

 

These methods can be used to programmatically determine which look-and-feel implementations are available, which is useful when building user interfaces which allow the end-user to dynamically select a look-and-feel.

 

The look-and-feel packages

 

The UI delegate classes provided in swing.plaf (ButtonUI, ScrollBarUI, and so on) define the precise API that a component can use to interact with the UI delegate instance. (NOTE: Interfaces were originally used here, but they were replaced with abstract classes because we felt the API was not mature enough to withstand the concrete casting of an interface.)

 

These plaf APIs are the root of all look-and-feel implementations.

Each look-and-feel implementation provides concrete subclasses of these abstract plaf classes. All such classes defined by a particular look-and-feel implementation are contained in a separate package under the swing.plaf package (for example,. swing.plaf.motif, swing.plaf.metal, and so on). A look-and-feel package contains the following:

 

·       The LookAndFeel subclass:

e.g., MetalLookAndFeel

 

·       All look-and-feel's UI delegate classes (for example, MetalButtonUI, MetalTreeUI, and the like).

 

·       Any look-and-feel utility classes such as the following classes:

       MetalGraphicsUtils

MetalIconFactory,

and so on.

 

·       Other resources associated with the look-and-feel, such as image files.

 

In implementing the various Swing look-and-feels, we soon discovered that there was a lot of commonality among them. We factored out this common code into a base look-and-feel implementation (called "basic") which extends the plaf abstract classes and from which the specific look-and-feel implementations (motif, windows, and so on.) extend. The basic look-and-feel package supports building a desktop-level look-and-feel, such as Windows or CDE/Motif.

 

The basic look-and-feel package is just one example of how to build a pluggable look-and-feel; the architecture is flexible enough to accommodate other approaches as well.

 

The remainder of this document will show how a look-and-feel package works at the generic level.

 

The LookAndFeel Subclass

 

The LookAndFeel class defines the following abstract methods, which all subclasses must implement:

 

   public String getName();

   public String getID();

   public String getDescription();

   public boolean isNativeLookAndFeel();

   public boolean isSupportedLookAndFeel();

 

The getName(), getID(), and getDescription() methods provide generic information about the look-and-feel.

 

The isNativeLookAndFeel() method returns true if the look-and-feel is native to the current platform. For example, MotifLookAndFeel returns true if it is currently running on the Solaris platform, and returns false otherwise.

 

The isSupportedLookAndFeel() method returns whether or not this look-and-feel is authorized to run on the current platform. For example, WindowsLookAndFeel returns true only if it is running on a Windows 95, Windows 98, or Windows NT machine.

 

A LookAndFeel class also provides methods for initialization and uninitialization:

 

   public void initialize()

   public void uninitialize()

 

The initialize() method is invoked by the UIManager when the LookAndFeel is made the "default" using the UIManager.setLookAndFeel() method.

 

uninitialize()is invoked by the UIManager when the LookAndFeel is about to be replaced as the default.

 

The Defaults Table

 

Finally, the LookAndFeel class provides a method to return the look-and-feel's implementation of the Defaults Table:

 

   public UIDefaults getDefaults()

 

The Defaults Table is represented by the UIDefaults class, a direct extension of java.util.Hashtable, which adds methods for accessing specific types of information about a look-and-feel. This table must include all the UIClassID-to-classname mapping information, as well as any default values for presentation-related properties (such as color, font, border, and icon) for each UI delegate. For example, following is a sample of what a fragment of getDefaults() might look like for a hypothetical look-and-feel in a package called "mine":

 

public UIDefaults getDefaults() {

   UIDefaults table = new UIDefaults();

   Object[] uiDefaults = {

 

     "ButtonUI",   "mine.MyButtonUI",

     "CheckBoxUI", "mine.MyCheckBoxUI",

     "MenuBarUI",  "mine.MyMenuBarUI",     

         ...

        

     "Button.background",

         new ColorUIResource(Color.gray),

     "Button.foreground",

         new ColorUIResource(Color.black),

     "Button.font",

         new FontUIResource("Dialog", Font.PLAIN, 12),

     "CheckBox.background",

         new ColorUIResource(Color.lightGray),

     "CheckBox.font",

         new FontUIResource("Dialog", Font.BOLD, 12),

 

         ...

   }

   table.putDefaults(uiDefaults);

   return table;

 

}

 

When the default look-and-feel is set with

 

UIManager.setLookAndFeel()

 

the UIManager calls getDefaults() on the new LookAndFeel instance and stores the hash table it returns. Subsequent calls to the UIManager's lookup methods will be applied to this table. For example, after making "mine" the default Look-and-Feel:

 

UIManager.get("ButtonUI") => "mine.MyButtonUI"

 

The UI classes access their default information in the same way. For example, our example ButtonUI class would initialize the JButton's "background" property like this:

 

button.setBackground(

    UIManager.getColor("Button.background");

 

The defaults are organized this way to allow developers to override them.

 

Distinguishing between UI-set and app-set properties

 

Swing allows applications to set property values (such as color and font) individually on components. So it's critical to make sure that these values don't get clobbered when a look-and-feel sets up its "default" properties for the component.

 

This is not an issue the first time a UI delegate is installed on a component (at construct time) because all properties will be uninitialized and legally settable by the look-and-feel. The problem occurs when the application sets individual properties after component construction and then subsequently sets a new look-and-feel (that is, dynamic look-and-feel switching). This means that the look-and-feel must be able to distinguish between property values set by the application, and those set by a look-and-feel.

 

This issue is handled by marking all values set by the look-and-feel with the plaf.UIResource interface. The plaf package provides a set of "marked" classes for representing these values:

 

ColorUIResource

FontUIResource

BorderUIResource

 

The preceding code example shows the usage of these classes to mark the default property values for the hypothetical MyButtonUI class.

 


The UI delegate

The superclass of all UI Delegate classes is:

 

swing.plaf.ComponentUI

 

This class contains the primary "machinery" for making the pluggable look-and-feel work. Its methods deal with UI installation and uninstallation, and with delegation of a component's geometry-handling and painting.

 

Many of the UI Delegate subclasses also provide additional methods specific to their own required interaction with the component; however, this document focuses primarily on the generic mechanism implemented by ComponentUI.

 

UI installation and deinstallation

 

First off, the ComponentUI class defines these methods methods

for UI delegate installation and uninstallation:

 

  public void installUI(JComponent c)

  public void uninstallUI(JComponent c)

 

Looking at the implementation of JComponent.setUI() (which is always invoked from the setUI method on JComponent subclasses), we can clearly see how UI delegate installation/de-installation works:

 

protected void setUI(ComponentUI newUI) {

   if (ui != null) {

      ui.uninstallUI(this);

   }

   ComponentUI oldUI = ui;

   ui = newUI;

   if (ui != null) {

      ui.installUI(this);

   }

   invalidate();

   firePropertyChange("UI", oldUI, newUI);

}


UI installation illustrated

The UI delegate's installUI() method is responsible for the following:

 

·       Set default font, color, border, and opacity properties on the component.

·       Install an appropriate layout manager on the component.

·       Add any appropriate child subcomponents to the component

·       Register any required event listeners on the component.

·       Register any look-and-feel-specific keyboard actions (mnemonics, etc.) for the component.

 

·       Register appropriate model listeners to be notified when to repaint.

·       Initialize any appropriate instance data.

 

For example, the installUI() method for an extension of ButtonUI might look like this:

 

protected MyMouseListener mouseListener;

protected MyChangeListener changeListener;  

 

public void installUI(JComponent c) {          

   AbstractButton b = (AbstractButton)c;

           

   // Install default colors & opacity

   Color bg = c.getBackground();

   if (bg == null || bg instanceof UIResource) {

      c.setBackground(

          UIManager.getColor("Button.background"));

   }

   Color fg = c.getForeground();

   if (fg == null || fg instanceof UIResource) {

      c.setForeground(

          UIManager.getColor("Button.foreground"));

   }

   c.setOpaque(false);

 

      // Install listeners  

   mouseListener = new MyMouseListener();

   c.addMouseListener(mouseListener);          

   c.addMouseMotionListener(mouseListener);

   changeListener = new MyChangeListener();

   b.addChangeListener(changeListener);

}

 

Conventions for initializing component properties

 

Swing defines a number of conventions for initializing component properties at install-time, including the following:

 

1.    All values used for setting colors, font, and border properties should be obtained from the Defaults table (as described in the subsection on the LookAndFeel subclass).

2.    Color, font and border properties should be set if -- and only if -- the application has not already set them.

 

To facilitate convention No 1, the UIManager class provides a number of static methods to extract property values of a particular type (for instance, the static methods:

 

UIManager.getColor()

UIManager.getFont()

etc.

 

Convention No. 2 is implemented by always checking for either a null value or an instance of UIResource before setting the property.

 

The ComponentUI's uninstall() method must carefully undo everything that was done in the installUI() method so that the component is left in a pristine state for the next UI delegate. The uninstall()method is responsible for:

 

·       Clearing the border property if it has been set by installUI().

·       Remove the layout manager if it had been set by installUI().

·       Remove any subcomponents added by installUI().

·       Remove any event/model listeners that were added by installUI().

·       Remove any look-and-feel-specific keyboard actions that were installed by installUI().

 

·       Nullify any initialized instance data (to allow GC to clean up).

 

For example, an uninstall() method to undo what we did in the above example installation might look like this:

 

public void uninstallUI(JComponent c) {

   AbstractButton b = (AbstractButton)c;

 

              

   // Uninstall listeners

   c.removeMouseListener(mouseListener);

   c.removeMouseMotionListener(mouseListener);

   mouseListener = null;

   b.removeChangeListener(changeListener);

   changeListener = null;

}

 

Defining geometry

 

In the AWT (and thus in Swing) a container's LayoutManager will layout the child components according to its defined algorithm; this is known as "validation" of a containment hierarchy. Typically LayoutManagers will query the child components' preferredSize property (and sometimes minimumSize and/or maximumSize as well, depending on the algorithm) in order to determine precisely how to position and size those children.

 

Obviously, these geometry properties are something that a look-and-feel usually needs to define for a given component, so ComponentUI provides the following methods for this purpose:

 

   public Dimension

      getPreferredSize(JComponent c)

   public Dimension

      getMinimumSize(JComponent c)

   public Dimension

      getMaximumSize(JComponent c)

   public boolean

      contains(JComponent c, int x, int y)

 

JComponent's parallel methods (which are invoked by the LayoutManager during validation) then simply delegate to the UI object's geometry methods if the geometry property was not explicitly set by the program. Below is the implementation of JComponent.getPreferredSize() which shows this delegation:

 

public Dimension getPreferredSize() {

   if (preferredSize != null) {

      return preferredSize;

   }

   Dimension size = null;

   if (ui != null) {

      size = ui.getPreferredSize(this);

   }

   return (size != null) ? size :

         super.getPreferredSize();

}

 

Even though the bounding box for all components is a Rectangle, it's possible to simulate a non-rectangular component by overriding the implementation of the contains() method from java.awt.Component. (This method is used for the hit-testing of mouse events). But, like the other geometry properties in Swing, the UI delegate defines its own version of the contains() method, which is also delegated to by

 

JComponent.contains():

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

   return (ui != null) ?

      ui.contains(this, x, y) :

            super.contains(x, y);

}

 

So a UI delegate could provide non-rectangular "feel" by defining a particular implementation of contains() (for example, if we wanted our MyButtonUI class to implement a button with rounded corners).

 

Painting

 

Finally, the UI delegate must paint the component appropriately, hence ComponentUI has the following methods:

 

public void paint(Graphics g, JComponent c)

public void update(Graphics g, JComponent c)

 

And once again, JComponent.paintComponent() takes care to delegate the painting:

 

protected void paintComponent(Graphics g) {

   if (ui != null) {

      Graphics scratchGraphics =

         SwingGraphics.createSwingGraphics(g.create());

      try {

         ui.update(scratchGraphics, this);

      }

      finally {

         scratchGraphics.dispose();

      }

   }

}

 

Similarly to the way in which things are done in AWT, the UI delegate's update() method clears the background (if opaque) and then invokes its paint() method, which is ultimately responsible for rendering the contents of the component.

 

Stateless vs. stateful delegates

 

All the methods on ComponentUI take a JComponent object as a parameter. This convention enables a stateless implementation of a UI delegate (because the delegate can always query back to the specified component instance for state information). Stateless UI delegate implementations allow a single UI delegate instance to be used for all instances of that component class, which can significantly reduce the number of objects instantiated.

 

This approach works well for many of the simpler GUI components. But for more complex components, we found it not to be a "win" because the inefficiency created by constant state recalculations was worse than creating extra objects (especially since the number of complex GUI components created in a given program tends to be small).

 

The ComponentUI class defines a static method for returning a delegate instance:

 

   public static ComponentUI

         createUI(JComponent c)

 

It's the implementation of this method that determines whether the delegate is stateless or stateful. That's because the UIManager.getUI() method invoked by the component to create the UI delegate internally invokes this createUI method on the delegate class to get the instance.

 

The Swing look-and-feel implementations use both types of delegates. For example, Swing's BasicButtonUI class implements a stateless delegate:

 

// Shared UI object

protected static ButtonUI buttonUI;

 

public static ComponentUI createUI(JComponent c)

   if(buttonUI == null) {

      buttonUI = new BasicButtonUI();

   }

   return buttonUI;

}

 

While Swing's BasicTabbedPaneUI uses the stateful approach:

 

public static ComponentUI createUI(JComponent c)

   return new BasicTabbedPaneUI();

}

 


Pluggable Look-and-Feel summary

 

The pluggable look-and-feel feature of Swing is both powerful and complex (which you understand if you've gotten this far!). It is designed to be programmed by a small subset of developers who have a particular need to develop a new look-and-feel implementation. In general, application developers only need to understand the capabilities of this mechanism in order to decide how they wish to support look-and-feels (such as whether to lock-down the program to a single look-and-feel or support look-and-feel configuration by the user). Swing's UIManager provides the API for applications to manage the look-and-feel at this level.