The Pluggable Look and Feel Architecture

Một phần của tài liệu the definitive guidae to java swing (Trang 755 - 797)

The Pluggable Look and Feel Architecture

In Chapter 19, you examined Swing’s drag-and-drop support. In this chapter, you will take an in-depth look at the pluggable look and feel (PLAF) architecture that’s available when you’re working with the Swing component library.

All aspects of the Swing components are Java-based. Therefore, no native source code exists, as there is with the AWT component set. If you don’t like the way the components are, you can change them, and you often have many ways to do so.

The abstract LookAndFeel class is the root class for a specific look and feel. Each one of the installable look and feel classes, as they’re described by the UIManager.LookAndFeelInfo class, must be a subclass of the LookAndFeel class. The LookAndFeel subclass describes the default appearance of Swing components for that specific look and feel.

The set of currently installed look and feel classes is provided by the UIManager class, which also manages the default display properties of all the components for a specific LookAndFeel.

These display properties are managed within a special UIDefaults hash table. The display prop- erties are either tagged with the empty UIResource interface or are UI delegates and therefore a subclass of the ComponentUI class. These properties can be stored as either UIDefaults.LazyValue objects or UIDefaults.ActiveValue objects, depending on their usage.

LookAndFeel Class

Implementations of the abstract LookAndFeel class describe how each of the Swing components will appear and how the user will interact with them. Each component’s appearance is controlled by a UI delegate, which serves as both the view and the controller in the MVC architecture.

Each of the predefined look and feel classes is contained within its own package, along with its associated UI delegate classes. When configuring the current look and feel, you can use one of the predefined look and feel classes or create your own. When you create your own look and feel, you can build on an existing look and feel, such as the BasicLookAndFeel class and its UI delegates, instead of creating all the UI delegates from scratch. Figure 20-1 shows the class hierarchy of the predefined look and feel classes.

742 C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E

Figure 20-1. LookAndFeel class hierarchy diagram

Each of the look and feel classes has six properties, as shown in Table 20-1.

These properties are all read-only and mostly describe the look and feel. The defaults property is slightly different, though. Once you get its UIDefaults value, you can then modify its state directly through its own methods. In addition, the UIDefaults for a LookAndFeel can be directly accessed and modified through the UIManager class.

ThenativeLookAndFeel property enables you to determine if a particular look and feel implementation is the native look and feel for the user’s operating system. For instance, the WindowsLookAndFeel is native to any system running one of the Microsoft Windows operating systems. The supportedLookAndFeel property tells you if a particular look and feel implementa- tion can be used. With the WindowsLookAndFeel implementation, this particular look and feel class is supported only if the current operating system is Microsoft Windows. Where available, theMacLookAndFeel implementation is supported only on MacOS computers. MotifLookAndFeel andMetalLookAndFeel are native look and feel classes that are not locked to a particular oper- ating system.

Listing the Installed Look and Feel Classes

To discover which look and feel classes are installed in your current environment, ask the UIManager, as shown in Listing 20-1. The UIManager has a UIManager.LookAndFeelInfo[]

getInstalledLookAndFeels() method that returns an array of objects providing the textual name (public String getName()) and class name (public String getClassName()) for all the installed look and feel classes.

Table 20-1. LookAndFeel Properties

Property Name Data Type Access

defaults UIDefaults Read-only

description String Read-only

ID String Read-only

name String Read-only

nativeLookAndFeel boolean Read-only

supportedLookAndFeel boolean Read-only

C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E 743

Listing 20-1. Listing Looking and Feel Classes

import javax.swing.*;

public class ListPlafs {

public static void main (String args[]) {

UIManager.LookAndFeelInfo plaf[] = UIManager.getInstalledLookAndFeels();

for (int i=0, n=plaf.length; i<n; i++) {

System.out.println("Name: " + plaf[i].getName());

System.out.println(" Class name: " + plaf[i].getClassName());

} } }

Running the program might generate the following output. Your current system configu- ration and/or changes to future versions of the Swing libraries could alter this result somewhat.

Name: Metal

Class name: javax.swing.plaf.metal.MetalLookAndFeel Name: CDE/Motif

Class name: com.sun.java.swing.plaf.motif.MotifLookAndFeel Name: Windows

Class name: com.sun.java.swing.plaf.windows.WindowsLookAndFeel

Note Ocean is not a look and feel in and of itself. Instead, it is a built-in theme of the Metal look and feel.

This theme happens to be the default for Metal.

Changing the Current Look and Feel

Once you know which look and feel classes are available on your system, you can have your programs use any one of them. The UIManager has two overloaded setLookAndFeel() methods for changing the installed look and feel class:

public static void setLookAndFeel(LookAndFeel newValue) throws UnsupportedLookAndFeelException

public static void setLookAndFeel(String className) throws

ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException

Although the first version might seem to be the more logical choice, the second one is the more frequently used version. When you ask for the installed look and feel classes with UIManager.getInstalledLookAndFeels(), you get back the class names as strings of the objects, not instances. Because of the exceptions that can occur when changing the look and feel, you need to place the setLookAndFeel() call within a try/catch block. If you’re changing the look and feel for an existing window, you need to tell the component to update its appearance with

744 C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E

a call to the public static void updateComponentTreeUI(Component rootComponent) method of SwingUtilities. If the component hasn’t been created yet, this isn’t necessary.

The following source fragment demonstrates changing a look and feel:

try {

UIManager.setLookAndFeel(finalLafClassName);

SwingUtilities.updateComponentTreeUI(frame);

} catch (Exception exception) { JOptionPane.showMessageDialog ( frame, "Can't change look and feel", "Invalid PLAF", JOptionPane.ERROR_MESSAGE);

}

Figure 20-2 illustrates the results of a demonstration program that can change the look and feel at runtime through either a JComboBox or JButton component. Frequently, you won’t want to allow a user to change the look and feel; you may just want to set the look and feel at startup time.

Figure 20-2. Before and after changing the look and feel

Listing 20-2 shows the complete source of the program shown in Figure 20-2.

Listing 20-2. Changing the Look and Feel

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.plaf.*;

public class ChangeLook {

public static void main (String args[]) { Runnable runner = new Runnable() { public void run() {

final JFrame frame = new JFrame("Change Look");

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E 745

ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Object source = actionEvent.getSource();

String lafClassName = null;

if (source instanceof JComboBox) { JComboBox comboBox = (JComboBox)source;

lafClassName = (String)comboBox.getSelectedItem();

} else if (source instanceof JButton) {

lafClassName = actionEvent.getActionCommand();

}

if (lafClassName != null) {

final String finalLafClassName = lafClassName;

Runnable runnable = new Runnable() { public void run() {

try {

UIManager.setLookAndFeel(finalLafClassName);

SwingUtilities.updateComponentTreeUI(frame);

} catch (Exception exception) { JOptionPane.showMessageDialog ( frame, "Can't change look and feel", "Invalid PLAF", JOptionPane.ERROR_MESSAGE);

} } };

EventQueue.invokeLater(runnable);

} } };

UIManager.LookAndFeelInfo looks[] = UIManager.getInstalledLookAndFeels();

DefaultComboBoxModel model = new DefaultComboBoxModel();

JComboBox comboBox = new JComboBox(model);

JPanel panel = new JPanel();

for (int i=0, n=looks.length; i<n; i++) {

JButton button = new JButton(looks[i].getName());

model.addElement(looks[i].getClassName());

button.setActionCommand(looks[i].getClassName());

button.addActionListener(actionListener);

panel.add(button);

}

comboBox.addActionListener(actionListener);

746 C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E

frame.add(comboBox, BorderLayout.NORTH);

frame.add(panel, BorderLayout.SOUTH);

frame.setSize(350, 150);

frame.setVisible(true);

} };

EventQueue.invokeLater(runner);

} }

Note Notice that the actual look and feel change is made in a call to EventQueue.invokeLater().

This is necessary because the handling of the current event must finish before you can change the look and feel, and the change must happen on the event queue.

Besides programmatically changing the current look and feel, you can start up a program from the command line with a new look and feel. Just set the swing.defaultlaf system property to the look and feel class name. For instance, the following startup line would start the ChangeLook program, making the Motif look and feel the initial look and feel.

java -Dswing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel ChangeLook If you want a different default look and feel every time a program starts up, you can create a file, swing.properties, under the Java runtime directory (jre by default) with the appropriate setting. The swing.properties file needs to be in the lib directory of the Java runtime directory (jre/lib). For instance, the following line would cause the initial look and feel to be Motif all the time, unless changed programmatically or from the command line.

swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel

In addition to the swing.defaultlaf setting, the swing.properties file supports several other entries, as listed in Table 20-2. Each property allows you to override the default settings for the predefined look and feel setup. The auxiliary and multiplexing look and feel classes support accessibility, among other things. They will be discussed later in this chapter, in the

“Using an Auxiliary Look and Feel” section.

Table 20-2. Swing Properties File Entries

Property Name Default Value When Unset

swing.defaultlaf javax.swing.plaf.metal.MetalLookAndFeel

swing.auxiliarylaf None

swing.plaf.multiplexinglaf javax.swing.plaf.multi.MultiLookAndFeel swing.installedlafs Metal, Motif, Windows

swing.installedlaf.*.name N/A swing.installedlaf.*.class N/A

C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E 747

Tip Theswing.installedlafs and swing.auxiliarylaf property settings are comma-separated lists of installed look and feel classes.

You may notice that the Synth class shown in the class hierarchy in Figure 20-1 is not listed in the default set of installed look and feel classes. Synth requires a secondary configuration file; it isn’t something you can just switch to on the fly without defining the custom appearances.

This base look and feel class provides the framework for customization. You’ll learn how to use the Synth look and feel in the “SynthLookAndFeel Class” section later in this chapter.

TheWindowsClassicLookAndFeel is used when the Windows XP style is not appropriate for the user’s platform or the swing.noxp system property is set.

Customizing the Current Look and Feel

In Chapter 3, you looked at the MVC architecture as well as how the Swing components combine the view and the controller into a UI delegate. Now, you will delve into the UI delegate for the Swing components. Basically, if you don’t like how a Swing component looks, you tell the UIManager to change it, and then it will never again look the way it did.

UIManager Class

Whenever you need to create a Swing component, the UIManager class acts as a proxy to get information about the currently installed look and feel. That way, if you want to install a new look and feel or change an existing one, you don’t need to tell the Swing components directly;

you just inform the UIManager.

Each discussion of components in earlier chapters has been accompanied by a table of all the settings that can be changed through the UIManager. In addition, this book’s appendix provides an alphabetical listing of all available settings for JDK 5.0. Once you know the property string for the setting you want to change, you call the public Object UIManager.put(Object key, Object value) method, which changes the property setting and returns the previous setting (if one existed). For instance, the following line changes the background to red for JButton components. After you put a new setting into the UIManager class lookup table, any components created in the future will use the new value, Color.RED.

UIManager.put("Button.background", Color.RED);

Once you place new settings into the lookup table for the UIManager, the new settings will be used when you create a new Swing component. Old components aren’t automatically updated; you must call their public void updateUI() method if you want them to be individu- ally updated (or call updateComponentTreeUI() to update a whole window of components). If you’re creating your own components, or you’re just curious about the current setting for one of the different component properties, you can ask the UIManager with one of the methods listed in Table 20-3.

748 C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E

Each of these methods, except getUI(), has a second version that accepts a Locale argument for localization support.

In addition to the defaults property, which is used when you call the different put() and get() methods, the UIManager has eight class-level properties. These are listed in Table 20-4, which includes two entries for lookAndFeel, with two different setter methods.

ThesystemLookAndFeelClassName property allows you to determine what the specific look and feel class name is for the user’s operating system. The crossPlatformLookAndFeelClassName prop- erty enables you to find out what class name, by default, represents the cross-platform look and feel:javax.swing.plaf.metal.MetalLookAndFeel. Initially, the lookAndFeelDefaults property and Table 20-3. UIManager UIDefaults Getter Methods

Method Name Return Type

getObject(Object key) Object getBorder(Object key) Border getColor(Object key) Color getDimension(Object key) Dimension

getFont(Object key) Font

getIcon(Object key) Icon

getInsets(Object key) Insets

getInt(Object key) int

getString(Object key) String getUI(JComponent component) ComponentUI

Table 20-4. UIManager Class Properties

Property Name Data Type Access

auxiliaryLookAndFeels LookAndFeel[ ] Read-only

crossPlatformLookAndFeelClassName String Read-only

defaults UIDefaults Read-only

installedLookAndFeels UIManager.LookAndFeelInfo[ ] Read-write

lookAndFeel LookAndFeel Read-write

lookAndFeel String Write-only

lookAndFeelDefaults UIDefaults Read-only

propertyChangeListeners PropertyChangeListener[ ] Read-only

systemLookAndFeelClassName String Read-only

C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E 749

thedefaults property are equivalent. When you want to make changes to the look and feel, you use the defaults property. That way, the settings for a predefined look and feel don’t change.

UIManager.LookAndFeelInfo Class

When you ask the UIManager for the list of installed look and feel classes, you’re returned an array of UIManager.LookAndFeelInfo objects. From this array, you can find out the descriptive name of the look and feel (from the name property of the LookAndFeel implementation), as well as the class name for the implementation. As Table 20-5 shows, the two settings are read-only.

UIDefaults Class

TheLookAndFeel classes and the UIManager use a special UIDefaults hash table to manage the Swing component properties that depend on the look and feel. The special behavior is that whenever a new setting is placed in the hash table with put(), a PropertyChangeEvent is generated and any registered PropertyChangeListener objects are notified. Many of the BasicLookAndFeel classes automatically register the UI delegate to be interested in property change events at the appropriate times.

If you need to change a number of properties at once, you can use the public void putDefaults(Object keyValueList[]) method, which causes only one notification event. With putDefaults(), the key/value entries alternate in a single-dimension array. For instance, to cause buttons to have a default background color of pink and a foreground color of magenta, you would use the following:

Object newSettings[] = {"Button.background", Color.PINK, "Button.foreground", Color.MAGENTA};

UIDefaults defaults = UIManager.getDefaults();

defaults.putDefaults(newSettings);

BecauseUIDefaults is a Hashtable subclass, you can discover all the installed settings by using anEnumeration to loop through all the keys or values. To simplify things a little, Listing 20-3 presents a program that lists the properties sorted within a JTable. It reuses several of the table sorting classes from Chapter 18.

Note Feel free to change the UIDefaults property lister program in Listing 20-3 to support modification of property values.

Table 20-5. UIManager.LookAndFeelInfo Properties

Property Name Data Type Access

className String Read-only

name String Read-only

750 C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E

Listing 20-3. Listing UIDefault Properties

import javax.swing.*;

import javax.swing.table.*;

import java.awt.*;

import java.awt.event.*;

import java.util.*;

public class ListProperties {

static class CustomTableModel extends AbstractTableModel { Vector<Object> keys = new Vector<Object>();

Vector<Object> values = new Vector<Object>();

private static final String columnNames[] = {"Property String", "Value"};

public int getColumnCount() { return columnNames.length;

}

public String getColumnName(int column) { return columnNames[column];

}

public int getRowCount() { return keys.size();

}

public Object getValueAt(int row, int column) { Object returnValue = null;

if (column == 0) {

returnValue = keys.elementAt(row);

} else if (column == 1) {

returnValue = values.elementAt(row);

}

return returnValue;

}

public synchronized void uiDefaultsUpdate(UIDefaults defaults) { Enumeration newKeys = defaults.keys();

keys.removeAllElements();

while (newKeys.hasMoreElements()) { keys.addElement(newKeys.nextElement());

}

C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E 751

Enumeration newValues = defaults.elements();

values.removeAllElements();

while (newValues.hasMoreElements()) {

values.addElement(newValues.nextElement());

}

fireTableDataChanged();

} }

public static void main(String args[]) { Runnable runner = new Runnable() { public void run() {

final JFrame frame = new JFrame("List Properties");

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

final CustomTableModel model = new CustomTableModel();

model.uiDefaultsUpdate(UIManager.getDefaults());

TableSorter sorter = new TableSorter(model);

JTable table = new JTable(sorter);

TableHeaderSorter.install(sorter, table);

table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);

UIManager.LookAndFeelInfo looks[] = UIManager.getInstalledLookAndFeels();

ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { final String lafClassName = actionEvent.getActionCommand();

Runnable runnable = new Runnable() { public void run() {

try {

UIManager.setLookAndFeel(lafClassName);

SwingUtilities.updateComponentTreeUI(frame);

model.uiDefaultsUpdate(UIManager.getDefaults());

} catch (Exception exception) { JOptionPane.showMessageDialog ( frame, "Can't change look and feel", "Invalid PLAF", JOptionPane.ERROR_MESSAGE);

} } };

EventQueue.invokeLater(runnable);

} };

752 C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E

JToolBar toolbar = new JToolBar();

for (int i=0, n=looks.length; i<n; i++) {

JButton button = new JButton(looks[i].getName());

button.setActionCommand(looks[i].getClassName());

button.addActionListener(actionListener);

toolbar.add(button);

}

frame.add(toolbar, BorderLayout.NORTH);

JScrollPane scrollPane = new JScrollPane(table);

frame.add(scrollPane, BorderLayout.CENTER);

frame.setSize(400, 400);

frame.setVisible(true);

} };

EventQueue.invokeLater(runner);

} }

Figure 20-3 shows an example of running the property lister.

Figure 20-3. Sample property lister display

Tip To reset a property to the default for the currently installed look and feel, set it to null. This will cause the component to get the original default from the look and feel.

C H A P T E R 2 0 ■ T H E P L U G G A B L E L O O K A N D F E E L A R C H I T E C T U R E 753

UIResource Interface

EveryUIDefaults setting for the predefined look and feel classes uses a special marker inter- face,UIResource, that lets the UI delegate determine if a default value has been overridden. If you’ve changed a specific setting to a new value (for example, the Button.background setting to Color.PINK), then the UIManager won’t replace this setting when the installed look and feel changes. This is also true of a call to setBackground(Color.PINK). Only when the value for a specific property implements the UIResource interface will the setting change when the look and feel changes.

Thejavax.swing.plaf package contains many classes that implement the UIResource interface. For example, the ColorUIResource class treats Color objects as UIResource elements.

Table 20-6 lists all of the predefined UIResource components available for customizing the installed look and feel.

Table 20-6. UIResource Collection

UIResource Implementation Wrapped Class/Interface

ActionMapUIResource ActionMap

BasicBorders.ButtonBorder Border

BasicBorders.FieldBorder Border

BasicBorders.MarginBorder Border

BasicBorders.MenuBarBorder Border

BasicBorders.RadioButtonBorder Border

BasicBorders.RolloverButtonBorder Border

BasicBorders.SplitPaneBorder Border

BasicBorders.ToggleButtonBorder Border

BasicComboBoxEditor.UIResource ComboBoxEditor BasicComboBoxRenderer.UIResource ListCellRenderer

BasicTextUI.BasicCaret Caret

BasicTextUI.BasicHighlighter Highlighter

BorderUIResource Border

BorderUIResource.BevelBorderUIResource Border BorderUIResource.CompoundBorderUIResource Border BorderUIResource.EmptyBorderUIResource Border BorderUIResource.EtchedBorderUIResource Border BorderUIResource.LineBorderUIResource Border BorderUIResource.MatteBorderUIResource Border BorderUIResource.TitledBorderUIResource Border

ColorUIResource Color

Một phần của tài liệu the definitive guidae to java swing (Trang 755 - 797)

Tải bản đầy đủ (PDF)

(912 trang)