Swing is a powerful enhancement to the AWT because it provides a rich set of platform independent GUI components. Platform independence ensures that Swing components are supported in the same manner across all Java ports. Platform independence also enables the look-and-feel (L&F) of Swing components to be easily tailored. This feature, referred to as Pluggable Look and Feel (PL&F), lets you create applets and applications that use a look and feel that is independent of the windowing platform in which the applets and applications are executed. For example, you can create applications that use a Macintosh look and feel when they are executed on a Windows platform, and you can create applets that use a Motif look and feel no matter what browsers or windowing systems are used to run them.
This chapter covers PL&F. It explains what look and feel is and how the model-view-controller (MVC) architecture is used to achieve PL&F. It introduces you to the Swing classes and interfaces that implement PL&F and shows you how to change an applet or application's look and feel. It also shows you how to develop your own look and feel. When you finish this chapter, you'll be able to use PL&F to enhance the style and consistency of the applets and applications you develop.
The look and feel of a program consists of the way the program presents itself to the user (its look) and the way the user interacts with it (its feel). Most programs display their output to the user's console and receive input from a keyboard and pointer device (mouse or equivalent). However, look and feel is subtler than raw input and output.
Many operating systems, such as Microsoft Windows, Macintosh OS, and UNIX, support windowing systems, which allow windows to be opened, closed, moved, and sized based on keyboard and mouse operations. They also support GUI components, such as labels, buttons, text fields, and menus. However, look and feel is subtler still.
Look and feel is determined by how a window or other GUI component is displayed and how it responds to user input. The GUI components of Microsoft Windows share a display style and operational behavior that set them apart from the equivalent components of the Macintosh. We can look at a screen capture and determine whether it came from Microsoft Windows or Macintosh based on its look. We can interact with a GUI and identify it as Motif based upon its feel. We notice slight differences in the controls used with windows, the way menus are displayed, and the way buttons behave when clicked that enable us to make these decisions. These differences are what constitute look and feel.
Swing provides the capability to change the look and feel of an applet or application. This capability stems from the fact that Swing components (unlike AWT components) are not tied to the GUI components of the native windowing system. This capability allows you to create applets or applications that will use a specific look and feel (such as Motif) no matter whether the applet or application executes on a Macintosh, a UNIX system, or a PC. Figure 14.1 provides an example of a Java program that uses the Motif look and feel but executes under Windows 95. Figure 14.2 shows the very same program but with a Windows look and feel. The capability to easily change look and feel, referred to as pluggable look and feel (PL&F), also allows custom look and feel to be developed. For example, Swing defines the Metal look and feel as the standard look and feel for Java applets and applications that use Swing components. The Metal look and feel is named for the shiny sharp characteristics of its GUI components. Figure 14.3 provides an example of the Metal look and feel.
FIGURE 14.1. The Motif look and feel.
FIGURE 14.2. The Windows look and feel.
Figure 14.3. The Metal look and feel.
PL&F is implemented in terms of the model-view-controller (MVC) architecture. MVC is a software architecture that separates the state of an object (the model), the way the object is displayed to the user (the view), and the way that the object's state is updated (the controller). By separating these three perspectives, it is possible to define GUI components that are equivalent in terms of information state (model), but are displayed (view) and respond to the user (controller) in different ways. For example, a button's state may be defined in terms of a text label, an icon graphic, and whether the button is being clicked. The button's view can allow the button's label and icon to be displayed in a great variety of ways: square button, round button, etched button, label-only, text-only, different colors, and so on. The button's controller can cause the clicking of the button to be clicked in different ways: single-click, double-click, right-click, left-click, and so on.
By separating the model from its view and controller, Swing allows logically equivalent buttons to be implemented but rendered with different look and feels. MVC provides other advantages. For example, you can define a single GUI component with multiple simultaneous views. As another example, you can define a component that provides a separate controller for disabled users.
In implementing PL&F, Swing allows GUI components to be tailored in terms of their model and L&F. L&F is a function of view and controller. The L&F of a component is implemented in terms of a delegate, which is the object that is used to display the component and interact with the user. The delegate encapsulates view and controller as a single object. This eliminates the need for all views to support all controllers, allowing L&F to exhibit richer, more varied behavior.
Each Swing component (subclass of JComponent) is defined in terms of a unique model and delegate. For example, the models of JButton components must implement the ButtonModel interface. However, this interface may be implemented in different ways by different classes. The model of a component is accessed via the getModel() and setModel() methods. Similarly, the delegates of JButton components must implement the ButtonUI interface. This interface can be implemented in different ways by different classes, thereby making PL&F possible for the JButton class. The delegate of a component is accessed via the getUI() and setUI() methods.
Delegates provide the basis for changing look and feel. In order to change the look and feel of a component, all you need to do is change the component's delegate. You change the component's delegate via the setUI() method. Consider the following example:
JButton button = new JButton("My Button"); button.setUI(new MotifButtonUI());
The button's delegate is changed to an object of the MotifButtonUI object.
The question naturally arises, "What delegates are available for a particular component?" The answer to this question lies in the PL&F packages that come with JDK 1.2 and the PL&F packages that you install. We'll cover the development and installation of custom PL&F packages later in this chapter in the section, "Look and Feel Programming." The PL&F packages that come with JDK 1.2 are as follows:
The Macintosh, Motif, and Windows looks and feels are modeled after the looks and feels of popular windowing systems. The Basic, Organic, and Metal looks and feels are Java-specific. Each look and feel package provides classes that implement the delegate interfaces of Swing components. For example, the com.sun.java.swing.plaf.motif package provides the MotifButtonUI class and the com.sun.java.swing.plaf.metal package provides the MetalButtonUI class.
Delegate classes allow you to change the look and feel of a single component or a group of components (one component at a time). But what if you want to change the look and feel for all of your components? The UIManager class of com.sun.java.swing provides the solution. The static setLookAndFeel() method of UIManager lets you set the look and feel of all of the components used by an applet or application. For example, the following statement sets the look and feel to the Motif look and feel.
try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.Motif"); }catch(Exception ex){ System.out.println(ex); }
There are two versions of setLookAndFeel(). One version takes the name of a look and feel package as a parameter. The other version takes the name of an object of the LookAndFeel class as a parameter. The first version throws the LookAndFeelException and the second version throws the ClassNotFound exception. After setting the look and feel, your GUI components must update their delegates to the new look and feel. The easiest way to do this is to invoke the static updateComponentTreeUI() method of the SwingUtilities class. This method asks each component that is contained in the component (or its subcomponents) passed as a parameter to update its delegate. For example, if this refers to a JFrame or JApplet object, all components contained in the application or applet will update their delegate to the new look and feel.
SwingUtilities.updateComponentTreeUI(this);
The next section provides an example application that changes its look and feel under user control.
The SwingLF application, shown in Listing 14.1, shows how look and feel can be dynamically changed during program execution. When you run the program, it uses the default Metal look and feel, as shown in Figure 14.4. Play around with the menus and GUI controls to familiarize yourself with the Metal L&F. When you are finished, click the Motif button, and the program changes its look and feel to the Motif L&F, as shown in Figure 14.5. If you are not already familiar with Motif, you may want to take the time to familiarize yourself at this time. Finally, click the Windows button, and the program changes its look and feel to the Windows look and feel, as shown in Figure 14.6. Which L&F do you prefer? I find the Metal L&F to be very appealing. Compared to Metal, the Windows L&F appears pretty boring.
FIGURE 14.4. The Metal L&F is the program's default L&F.
FIGURE 14.5. The Motif L&F as displayed by SwingLF.
FIGURE 14.6. SwingLF also displays the Windows L&F.
import java.awt.*; import java.awt.event.*; import com.sun.java.awt.swing.*; import com.sun.java.awt.swing.event.*; import com.sun.java.awt.swing.border.*; import com.sun.java.awt.swing.plaf.motif.*; import com.sun.java.awt.swing.plaf.metal.*; import com.sun.java.awt.swing.plaf.windows.*; public class SwingLF extends JFrame { public static int WIDTH = 450; public static int HEIGHT = 450; public static String TITLE = "SwingLF"; Container frameContainer; // Swing components JPanel[] panels = new JPanel[6]; JCheckBox checkbox1 = new JCheckBox("Check 1"); JCheckBox checkbox2 = new JCheckBox("Check 2"); JCheckBox checkbox3 = new JCheckBox("Check 3"); ButtonGroup buttonGroup = new ButtonGroup(); JRadioButton radioButton1 = new JRadioButton("Radio 1"); JRadioButton radioButton2 = new JRadioButton("Radio 2"); JRadioButton radioButton3 = new JRadioButton("Radio 3"); JTextField textField1 = new JTextField("Text field 1",15); JTextField textField2 = new JTextField("Text field 2",15); JSlider slider1 = new JSlider(0,0,100,25); JSlider slider2 = new JSlider(0,0,100,75); JButton metalButton = new JButton("Metal"); JButton motifButton = new JButton("Motif"); JButton windowsButton = new JButton("Windows"); JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); JMenuItem fileNew = new JMenuItem("New"); JMenuItem fileOpen = new JMenuItem("Open"); JMenuItem fileSave = new JMenuItem("Save"); JMenuItem fileExit = new JMenuItem("Exit"); JMenu editMenu = new JMenu("Edit"); JMenuItem editCut = new JMenuItem("Cut"); JMenuItem editCopy = new JMenuItem("Copy"); JMenuItem editPaste = new JMenuItem("Paste"); //Look and Feel Classes MetalLookAndFeel metalLF = new MetalLookAndFeel(); MotifLookAndFeel motifLF = new MotifLookAndFeel(); WindowsLookAndFeel windowsLF = new WindowsLookAndFeel(); public SwingLF() { super(TITLE); buildGUI(); setupEventHandlers(); setSize(WIDTH,HEIGHT); show(); } void buildGUI() { setupMenuBar(); layoutComponents(); } void setupMenuBar() {
fileMenu.add(fileNew);
fileMenu.add(fileOpen); fileMenu.add(fileSave); fileMenu.add(fileExit); editMenu.add(editCut); editMenu.add(editCopy); editMenu.add(editPaste); menuBar.add(fileMenu); menuBar.add(editMenu); setJMenuBar(menuBar); } public void layoutComponents() { for(int i=0;i<panels.length;++i) panels[i] = new JPanel(); panels[0].setBorder(new TitledBorder("Checkboxes")); panels[0].setLayout(new GridLayout(3,1)); panels[0].add(checkbox1); panels[0].add(checkbox2); panels[0].add(checkbox3); panels[1].setBorder(new TitledBorder("Radio Buttons")); panels[1].setLayout(new GridLayout(3,1)); panels[1].add(radioButton1); panels[1].add(radioButton2); panels[1].add(radioButton3); panels[2].setBorder(new TitledBorder("Text Fields")); panels[2].add(textField1); panels[2].add(textField2); panels[3].setBorder(new TitledBorder("Sliders")); panels[3].add(slider1); panels[3].add(slider2); panels[4].setLayout(new GridLayout(3,1)); panels[4].add(metalButton); panels[4].add(motifButton); panels[4].add(windowsButton); frameContainer = getContentPane(); frameContainer.setLayout(new GridLayout(3,2)); for(int i=0;i<panels.length;++i) frameContainer.add(panels[i]); } void setupEventHandlers() { addWindowListener(new WindowHandler()); fileExit.addActionListener(new MenuItemHandler()); metalButton.addActionListener(new ButtonHandler()); motifButton.addActionListener(new ButtonHandler()); windowsButton.addActionListener(new ButtonHandler()); } public static void main(String[] args) { SwingLF app = new SwingLF(); } public class WindowHandler extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public class MenuItemHandler implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0); } } public class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd.equals("Motif")) { try { UIManager.setLookAndFeel(motifLF); SwingUtilities.updateComponentTreeUI(SwingLF.this); }catch(Exception ex){ System.out.println(ex); } }else if(cmd.equals("Metal")) { try { UIManager.setLookAndFeel(metalLF); SwingUtilities.updateComponentTreeUI(SwingLF.this); }catch(Exception ex){ System.out.println(ex); } }else if(cmd.equals("Windows")) { try { UIManager.setLookAndFeel(windowsLF); SwingUtilities.updateComponentTreeUI(SwingLF.this); }catch(Exception ex){ System.out.println(ex); } } } }
SwingLF begins by importing the packages that are used to implement the various L&Fs. It then declares the class constants and the Swing components to be displayed in the application window. You should be familiar with these components from Chapter 12, "Introducing Swing," and Chapter 13, "Working with Swing Components." Three look and feel variables are declared and used to refer to objects of the MetalLookAndFeel, MotifLookAndFeel, and WindowsLookAndFeel classes.
The SwingLF constructor sets its title, invokes buildGUI() to build the application's GUI and setupEventHandlers() to set up event handling, and then sizes and displays the window.
The buildGUI() method simply invokes setupMenuBar() and layoutComponents(). The setupMenuBar() method sets up the application's menu bar and the layoutComponents() method adds and arranges the previously declared Swing components to the application's GUI.
The setupEventHandlers()method sets up the event handling for the window's closing, the Exit menu item, and the three buttons. Only the ButtonHandler is of interest. The clicking of a button is handled by invoking getActionCommand() to get the label of the button that was clicked. The program's look and feel is then changed based on the button. The static setLookAndFeel() method of UIManager is used to change the look and feel. The static updateComponentTreeUI() method of SwingUtilities is used to notify the program's GUI controls to update their L&F to the new delegates.
Changing the delegate of a component results in a change in the component's look and feel. You can also change a component's model. However, this is rarely done because the state of a label, button, text field and other GUI components tends to be the same from platform-to-platform and across various look and feels. However, there are times when you may want to add state information to a component's model. As a hypothetical example, suppose you want to develop an animated button. In this case, you may want to store the button's animation frames as part of its model. Any new implementation of a component's model must implement the interface associated with that model. For example, the hypothetical AnimatedButton model would need to implement the ButtonModel interface. You could then change a button's model to AnimatedButton using the setModel() method, as follows:
JButton myCoolButton = new JButton("Cool Button"); myCoolButton.setModel(new AnimatedButton("frames.zip"));
The model of myCoolButton is set to an object of the AnimatedButton class. This object is created using the frames.zip file as an argument. This file would contain the frames of the animation sequence.
Now that you know what look and feel is, how it is implemented in terms of the MVC architecture, and how to use different looks and feels, you're probably wondering how you would go about developing your own look and feel. The answer is to create delegate classes for the components of your look and feel. The easiest way to do this is to extend an existing look and feel, such as the Basic L&F. You can then change selected GUI components and leave the others as they are. You would need to override some methods of the BasicLookAndFeel class and use the initClassDefaults() method to map component classes to your look and feel delegates.
Listing 14.2 shows the RedLookAndFeel class, which extends the BasicLookAndFeel class. This class overrides most of the methods inherited from BasicLookAndFeel.
The initClassDefaults() method is used to modify the default mapping between delegate interfaces and the L&F classes that implement those interfaces. The only change to the delegate mapping is the RedButtonUI class used to implement the look and feel for buttons. Whenever an object of the ButtonUI is required, an object of the RedButtonUI is used.
import java.awt.*; import java.awt.event.*; import com.sun.java.awt.swing.*; import com.sun.java.awt.swing.event.*; import com.sun.java.awt.swing.plaf.*; import com.sun.java.awt.swing.plaf.basic.*; public class RedLookAndFeel extends BasicLookAndFeel { public RedLookAndFeel() { super(); } public String getName() { return "Red Look and Feel"; } public String getDescription() { return "The Red Look and Feel"; } public String getID() { return "RedLookAndFeel"; } public boolean isNativeLookAndFeel() { return false; } public boolean isSupportedLookAndFeel() { return true; } protected void initClassDefaults(UIDefaults table) { super.initClassDefaults(table); table.put("ButtonUI", "RedButtonUI"); } }
The RedButtonUI class (Listing 14.3) implements the button delegate for the RedLookAndFeel. All other delegates are inherited from BasicLookAndFeel. The RedButtonUI class extends the BasicButtonUI class of com.sun.java.swing. plaf.basic.
The static createUI() method is overridden to return an object of the RedButtonUI class.
The paintFocus() method is overridden to paint a red square around the perimeter of the button. The thickness of the red square is 5% of the button's height and 10% of the button's width.
Listing 14.3. The REDBUTTONUI class.
import java.awt.*; import java.awt.event.*; import com.sun.java.awt.swing.*; import com.sun.java.awt.swing.event.*; import com.sun.java.awt.swing.plaf.*; import com.sun.java.awt.swing.plaf.basic.*; public class RedButtonUI extends BasicButtonUI { public RedButtonUI() { super(); } public static ComponentUI createUI(JComponent c) { return new RedButtonUI(); } public void paintFocus(Graphics g, Dimension size) { Color color = g.getColor(); g.setColor(Color.red); int hpercentage = 5; int wpercentage = 10; int w = size.width; int h = size.height; int sw = w/wpercentage; int sh = h/hpercentage; g.fillRect(0,0,w,sh); g.fillRect(0,0,sw,h); g.fillRect(w-sw,0,sw,h); g.fillRect(0,h-sh,w,sh); g.setColor(color); } }
The MyLF program, shown in Listing 14.4, extends the SwingLF program of Listing 14.1 to use the RedLookAndFeel. When you run the program, it displays the opening window shown in Figure 14.7. Note the additional Red button. Click the Red button to use the Red look and feel. The Red look and feel uses the basic look and feel, except that it displays a red square around a button when it has the input focus, as shown in Figure 14.8.
FIGURE 14.7. The opening display of the MyLF program.
FIGURE 14.8. Switching to the RedLookAndFeel.
Press the Tab key to move the input focus to the Windows button.
Only a few changes are required to upgrade SwingLF to MyLF. The Red button is created, assigned an event handler, and displayed. The actionPerformed() method of ButtonHandler is updated to process the handling of the Red button and change the look and feel to the RedLookAndFeel class.
import java.awt.*; import java.awt.event.*; import com.sun.java.awt.swing.*; import com.sun.java.awt.swing.event.*; import com.sun.java.awt.swing.border.*; import com.sun.java.awt.swing.plaf.motif.*; import com.sun.java.awt.swing.plaf.metal.*; import com.sun.java.awt.swing.plaf.windows.*; public class MyLF extends JFrame { public static int WIDTH = 450; public static int HEIGHT = 450; public static String TITLE = "MyLF"; Container frameContainer; // Swing components JPanel[] panels = new JPanel[6]; JCheckBox checkbox1 = new JCheckBox("Check 1"); JCheckBox checkbox2 = new JCheckBox("Check 2"); JCheckBox checkbox3 = new JCheckBox("Check 3"); ButtonGroup buttonGroup = new ButtonGroup(); JRadioButton radioButton1 = new JRadioButton("Radio 1"); JRadioButton radioButton2 = new JRadioButton("Radio 2"); JRadioButton radioButton3 = new JRadioButton("Radio 3"); JTextField textField1 = new JTextField("Text field 1",15); JTextField textField2 = new JTextField("Text field 2",15); JSlider slider1 = new JSlider(0,0,100,25); JSlider slider2 = new JSlider(0,0,100,75); JButton metalButton = new JButton("Metal"); JButton motifButton = new JButton("Motif"); JButton windowsButton = new JButton("Windows"); JButton redButton = new JButton("Red"); JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); JMenuItem fileNew = new JMenuItem("New");
JMenuItem fileOpen = new JMenuItem("Open");
JMenuItem fileSave = new JMenuItem("Save"); JMenuItem fileExit = new JMenuItem("Exit"); JMenu editMenu = new JMenu("Edit"); JMenuItem editCut = new JMenuItem("Cut"); JMenuItem editCopy = new JMenuItem("Copy"); JMenuItem editPaste = new JMenuItem("Paste"); //Look and Feel Classes MetalLookAndFeel metalLF = new MetalLookAndFeel(); MotifLookAndFeel motifLF = new MotifLookAndFeel(); WindowsLookAndFeel windowsLF = new WindowsLookAndFeel(); RedLookAndFeel redLF = new RedLookAndFeel(); public MyLF() { super(TITLE); buildGUI(); setupEventHandlers(); setSize(WIDTH,HEIGHT); show(); } void buildGUI() { setupMenuBar(); layoutComponents(); } void setupMenuBar() { fileMenu.add(fileNew); fileMenu.add(fileOpen); fileMenu.add(fileSave); fileMenu.add(fileExit); editMenu.add(editCut); editMenu.add(editCopy); editMenu.add(editPaste); menuBar.add(fileMenu); menuBar.add(editMenu); setJMenuBar(menuBar); } public void layoutComponents() { for(int i=0;i<panels.length;++i) panels[i] = new JPanel(); panels[0].setBorder(new TitledBorder("Checkboxes")); panels[0].setLayout(new GridLayout(3,1)); panels[0].add(checkbox1); panels[0].add(checkbox2); panels[0].add(checkbox3); panels[1].setBorder(new TitledBorder("Radio Buttons")); panels[1].setLayout(new GridLayout(3,1)); panels[1].add(radioButton1); panels[1].add(radioButton2); panels[1].add(radioButton3); panels[2].setBorder(new TitledBorder("Text Fields")); panels[2].add(textField1); panels[2].add(textField2); panels[3].setBorder(new TitledBorder("Sliders")); panels[3].add(slider1); panels[3].add(slider2); panels[4].setLayout(new GridLayout(2,1)); panels[4].add(metalButton); panels[4].add(motifButton); panels[5].setLayout(new GridLayout(2,1)); panels[5].add(redButton); panels[5].add(windowsButton); frameContainer = getContentPane(); frameContainer.setLayout(new GridLayout(3,2)); for(int i=0;i<panels.length;++i) frameContainer.add(panels[i]); } void setupEventHandlers() { addWindowListener(new WindowHandler()); fileExit.addActionListener(new MenuItemHandler()); metalButton.addActionListener(new ButtonHandler()); motifButton.addActionListener(new ButtonHandler()); windowsButton.addActionListener(new ButtonHandler()); redButton.addActionListener(new ButtonHandler()); } public static void main(String[] args) { MyLF app = new MyLF(); } public class WindowHandler extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public class MenuItemHandler implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0);
}
} public class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if(cmd.equals("Motif")) { try { UIManager.setLookAndFeel(motifLF); SwingUtilities.updateComponentTreeUI(MyLF.this); }catch(Exception ex){ System.out.println(ex); } }else if(cmd.equals("Metal")) { try { UIManager.setLookAndFeel(metalLF); SwingUtilities.updateComponentTreeUI(MyLF.this); }catch(Exception ex){ System.out.println(ex); } }else if(cmd.equals("Windows")) { try { UIManager.setLookAndFeel(windowsLF); SwingUtilities.updateComponentTreeUI(MyLF.this); }catch(Exception ex){ System.out.println(ex); } }else{ try { UIManager.setLookAndFeel(redLF); SwingUtilities.updateComponentTreeUI(MyLF.this); }catch(Exception ex){ System.out.println(ex); } } } } }
This chapter covers Swing's PL&F capabilities. It explains what look and feel is and how the model-view-controller (MVC) architecture is used to achieve PL&F. It introduces you to the Swing classes and interfaces that implement PL&F and shows you how to change an applet and application's look and feel. It also shows you how to develop your own look and feel. In the next chapter, you'll learn how to perform clipboard cut and paste operations using Java.
© Copyright 1998, Macmillan Computer Publishing. All rights reserved.