1.7 Modifying the Look and Feel

«« Previous
Next »»

This lesson tells you how to change the look and feel of your Swing application. The "look" of an application refers to its appearance. The "feel" refers to how the widgets behave. You can choose to use the default Swing look and feel (e.g. the Ocean theme for the Metal look and feel), or the look and feel of the native platform (e.g. Windows, GTK+), or you can customize your own look and feel.

◈ How to Set the Look and Feel

The architecture of Swing is designed so that you may change the "look and feel" (L&F) of your application's GUI. "Look" refers to the appearance of GUI widgets (more formally, JComponents) and "feel" refers to the way the widgets behave.

Swing's architecture enables multiple L&Fs by separating every component into two distinct classes: a JComponent subclass and a corresponding ComponentUI subclass. For example, every JList instance has a concrete implementation of ListUI (ListUI extends ComponentUI). The ComponentUI subclass is referred to by various names in Swing's documentation—"the UI," "component UI," "UI delegate," and "look and feel delegate" are all used to identify the ComponentUI subclass.

Most developers never need to interact with the UI delegate directly. For the most part, the UI delegate is used internally by the JComponent subclass for crucial functionality, with cover methods provided by the JComponent subclass for all access to the UI delegate. For example, all painting in JComponent subclasses is delegated to the UI delegate. By delegating painting, the 'look' can vary depending upon the L&F.

It is the responsibility of each L&F to provide a concrete implementation for each of the ComponentUI subclasses defined by Swing. For example, the Java Look and Feel creates an instance of MetalTabbedPaneUI to provide the L&F for JTabbedPane. The actual creation of the UI delegate is handled by Swing for you—for the most part you never need to interact directly with the UI delegate.
The rest of this section discusses the following subjects:

1. Available Look and Feels

Sun's JRE provides the following L&Fs:

◉ CrossPlatformLookAndFeel—this is the "Java L&F" (also called "Metal") that looks the same on all platforms. It is part of the Java API (javax.swing.plaf.metal) and is the default that will be used if you do nothing in your code to set a different L&F.

◉ SystemLookAndFeel—here, the application uses the L&F that is native to the system it is running on. The System L&F is determined at runtime, where the application asks the system to return the name of the appropriate L&F.

◉ Synth—the basis for creating your own look and feel with an XML file.

◉ Multiplexing— a way to have the UI methods delegate to a number of different look and feel implementations at the same time.

For Linux and Solaris, the System L&Fs are "GTK+" if GTK+ 2.2 or later is installed, "Motif" otherwise. For Windows, the System L&F is "Windows," which mimics the L&F of the particular Windows OS that is running—classic Windows, XP, or Vista. The GTK+, Motif, and Windows L&Fs are provided by Sun and shipped with the Java SDK and JRE, although they are not part of the Java API.

Apple provides its own JVM which includes their proprietary L&F.

In summary, when you use the SystemLookAndFeel, this is what you will see:

Platform Look and Feel
Solaris, Linux with GTK+ 2.2 or later GTK+ 
Other Solaris, Linux Motif
IBM UNIX IBM*
HP UX HP*
Classic Windows Windows
Windows XP Windows XP
Windows Vista Windows Vista
Macintosh Macintosh*

Supplied by the system vendor.

You don't see the System L&F in the API. The GTK+, Motif, and Windows packages that it requires are shipped with the Java SDK as:

com.sun.java.swing.plaf.gtk.GTKLookAndFeel
com.sun.java.swing.plaf.motif.MotifLookAndFeel
com.sun.java.swing.plaf.windows.WindowsLookAndFeel

Note that the path includes java, and not javax.

Note: The GTK+ L&F will only run on UNIX or Linux systems with GTK+ 2.2 or later installed, while the Windows L&F runs only on Windows systems. Like the Java (Metal) L&F, the Motif L&F will run on any platform.

All of Sun's L&Fs have a great deal of commonality. This commonality is defined in the Basic look and feel in the API (javax.swing.plaf.basic). The Motif and Windows L&Fs are each built by extending the UI delegate classes in javax.swing.plaf.basic (a custom L&F can be built by doing the same thing). The "Basic" L&F is not used without being extended.

In the API you will see four L&F packages:

◉ javax.swing.plaf.basic—basic UI Delegates to be extended when creating a custom L&F

◉ javax.swing.plaf.metal—the Java L&F, also known as the CrossPlatform L&F ("Metal" was the Sun project name for this L&F) The current default "theme" (discussed below) for this L&F is "Ocean, so this is often referred to as the Ocean L&F.

◉ javax.swing.plaf.multi—a multiplexing look and feel that allows the UI methods to delegate to a number of look and feel implementations at the same time. It can be used to augment the behavior of a particular look and feel, for example with a L&F that provides audio cues on top of the Windows look and feel. This is a way of creating a handicapped-accessible look and feel.

◉ javax.swing.plaf.synth—an easily configured L&F using XML files (discussed in the next section of this lesson)

You aren't limited to the L&Fs supplied with the Java platform. You can use any L&F that is in your program's class path. External L&Fs are usually provided in one or more JAR files that you add to your program's class path at runtime. For example:

java -classpath .;C:\java\lafdir\customlaf.jar YourSwingApplication

Once an external L&F is in your program's class path, your program can use it just like any of the L&Fs shipped with the Java platform.

2. Programatically Setting the Look and Feel


Note: If you are going to set the L&F, you should do it as the very first step in your application. Otherwise you run the risk of initializing the Java L&F regardless of what L&F you've requested. This can happen inadvertently when a static field references a Swing class, which causes the L&F to be loaded. If no L&F has yet been specified, the default L&F for the JRE is loaded. For Sun's JRE the default is the Java L&F, for Apple's JRE the Apple L&F, and so forth.

The L&F that Swing components use is specified by way of the UIManager class in the javax.swing package. Whenever a Swing component is created,the component asks the UI manager for the UI delegate that implements the component's L&F. For example, each JLabel constructor queries the UI manager for the UI delegate object appropriate for the label. It then uses that UI delegate object to implement all of its drawing and event handling.

To programatically specify a L&F, use the UIManager.setLookAndFeel() method with the fully qualified name of the appropriate subclass of LookAndFeel as its argument. For example, the bold code in the following snippet makes the program use the cross-platform Java L&F:

public static void main(String[] args) {
    try {
            // Set cross-platform Java L&F (also called "Metal")
        UIManager.setLookAndFeel(
            UIManager.getCrossPlatformLookAndFeelClassName());
    }
    catch (UnsupportedLookAndFeelException e) {
       // handle exception
    }
    catch (ClassNotFoundException e) {
       // handle exception
    }
    catch (InstantiationException e) {
       // handle exception
    }
    catch (IllegalAccessException e) {
       // handle exception
    }
     
    new SwingApplication(); //Create and show the GUI.
}
Alternatively, this code makes the program use the System L&F:

public static void main(String[] args) {
    try {
            // Set System L&F
        UIManager.setLookAndFeel(
            UIManager.getSystemLookAndFeelClassName());
    }
    catch (UnsupportedLookAndFeelException e) {
       // handle exception
    }
    catch (ClassNotFoundException e) {
       // handle exception
    }
    catch (InstantiationException e) {
       // handle exception
    }
    catch (IllegalAccessException e) {
       // handle exception
    }

    new SwingApplication(); //Create and show the GUI.
}

You can also use the actual class name of a Look and Feel as the argument to UIManager.setLookAndFeel(). For example,

// Set cross-platform Java L&F (also called "Metal")
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
or

// Set Motif L&F on any platform
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
You aren't limited to the preceding arguments. You can specify the name for any L&F that is in your program's class path.

3. Specifying the Look and Feel: Command Line


You can specify the L&F at the command line by using the -D flag to set the swing.defaultlaf property. For example:

java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel MyApp

java -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel MyApp

4. Specifying the Look and Feel: swing.properties


Yet another way to specify the current L&F is to use the swing.properties file to set the swing.defaultlaf property. This file, which you may need to create, is located in the lib directory of Sun's Java release (other vendors of Java may use a different location). For example, if you're using the Java interpreter in javaHomeDirectory\bin, then the swing.properties file (if it exists) is in javaHomeDirectory\lib. Here is an example of the contents of a swing.properties file:

# Swing properties
swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel

5. How the UI Manager Chooses the Look and Feel


Here are the look-and-feel determination steps that occur when the UI manager needs to set a L&F:

1. If the program sets the L&F before a look and feel is needed, the UI manager tries to create an instance of the specified look-and-feel class. If successful, all components use that L&F.
2. If the program hasn't successfully specified a L&F, then the UI manager uses the L&F specified by the swing.defaultlaf property. If the property is specified in both the swing.properties file and on the command line, the command-line definition takes precedence.
3. If none of these steps has resulted in a valid L&F, Sun's JRE uses the Java L&F. Other vendors, such as Apple, will use their default L&F.

6. Changing the Look and Feel After Startup


You can change the L&F with setLookAndFeel even after the program's GUI is visible. To make existing components reflect the new L&F, invoke the SwingUtilities updateComponentTreeUI method once per top-level container. Then you might wish to resize each top-level container to reflect the new sizes of its contained components. For example:

UIManager.setLookAndFeel(lnfName);
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();

7. An Example


In the following example, LookAndFeelDemo.java, you can experiment with different Look and Feels. The program creates a simple GUI with a button and a label. Every time you click the button, the label increments.

You can change the L&F by changing the LOOKANDFEEL constant on line 18. The comments on the preceding lines tell you what values are acceptable:

    // Specify the look and feel to use by defining the LOOKANDFEEL constant
    // Valid values are: null (use the default), "Metal", "System", "Motif",
    // and "GTK"
    final static String LOOKANDFEEL = "Motif";
Here the constant is set to "Motif", which is a L&F that will run on any platform (as will the default, "Metal"). "GTK+" will not run on Windows, and "Windows" will run only on Windows. If you choose a L&F that will not run, you will get the Java, or Metal, L&F.

In the section of the code where the L&F is actually set, you will see several different ways to set it, as discussed above:

if (LOOKANDFEEL.equals("Metal")) {
   lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
   //  an alternative way to set the Metal L&F is to replace the
   // previous line with:
   // lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
You can verify that both arguments work by commenting/un-commenting the two alternatives.

Here is a listing of the LookAndFeelDemo source file:


package lookandfeel;

import javax.swing.*;       
import java.awt.*;
import java.awt.event.*;
import javax.swing.plaf.metal.*;

public class LookAndFeelDemo implements ActionListener {
    private static String labelPrefix = "Number of button clicks: ";
    private int numClicks = 0;
    final JLabel label = new JLabel(labelPrefix + "0    ");

    // Specify the look and feel to use by defining the LOOKANDFEEL constant
    // Valid values are: null (use the default), "Metal", "System", "Motif",
    // and "GTK"
    final static String LOOKANDFEEL = "Metal";
 
    // If you choose the Metal L&F, you can also choose a theme.
    // Specify the theme to use by defining the THEME constant
    // Valid values are: "DefaultMetal", "Ocean",  and "Test"
    final static String THEME = "Test";
 

    public Component createComponents() {
        JButton button = new JButton("I'm a Swing button!");
        button.setMnemonic(KeyEvent.VK_I);
        button.addActionListener(this);
        label.setLabelFor(button);

        JPanel pane = new JPanel(new GridLayout(0, 1));
        pane.add(button);
        pane.add(label);
        pane.setBorder(BorderFactory.createEmptyBorder(
                                        30, //top
                                        30, //left
                                        10, //bottom
                                        30) //right
                                        );

        return pane;
    }

    public void actionPerformed(ActionEvent e) {
        numClicks++;
        label.setText(labelPrefix + numClicks);
    }

    private static void initLookAndFeel() {
        String lookAndFeel = null;
     
        if (LOOKANDFEEL != null) {
            if (LOOKANDFEEL.equals("Metal")) {
                lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
              //  an alternative way to set the Metal L&F is to replace the
              // previous line with:
              // lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
             
            }
         
            else if (LOOKANDFEEL.equals("System")) {
                lookAndFeel = UIManager.getSystemLookAndFeelClassName();
            }
         
            else if (LOOKANDFEEL.equals("Motif")) {
                lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
            }
         
            else if (LOOKANDFEEL.equals("GTK")) {
                lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
            }
         
            else {
                System.err.println("Unexpected value of LOOKANDFEEL specified: "
                                   + LOOKANDFEEL);
                lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
            }

            try {
           
           
                UIManager.setLookAndFeel(lookAndFeel);
             
                // If L&F = "Metal", set the theme
             
                if (LOOKANDFEEL.equals("Metal")) {
                  if (THEME.equals("DefaultMetal"))
                     MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
                  else if (THEME.equals("Ocean"))
                     MetalLookAndFeel.setCurrentTheme(new OceanTheme());
                  else
                     MetalLookAndFeel.setCurrentTheme(new TestTheme());
                   
                  UIManager.setLookAndFeel(new MetalLookAndFeel());
                }
               
               
               
             
            }
         
            catch (ClassNotFoundException e) {
                System.err.println("Couldn't find class for specified look and feel:"
                                   + lookAndFeel);
                System.err.println("Did you include the L&F library in the class path?");
                System.err.println("Using the default look and feel.");
            }
         
            catch (UnsupportedLookAndFeelException e) {
                System.err.println("Can't use the specified look and feel ("
                                   + lookAndFeel
                                   + ") on this platform.");
                System.err.println("Using the default look and feel.");
            }
         
            catch (Exception e) {
                System.err.println("Couldn't get specified look and feel ("
                                   + lookAndFeel
                                   + "), for some reason.");
                System.err.println("Using the default look and feel.");
                e.printStackTrace();
            }
        }
    }

    private static void createAndShowGUI() {
        //Set the look and feel.
        initLookAndFeel();

        //Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);

        //Create and set up the window.
        JFrame frame = new JFrame("SwingApplication");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        LookAndFeelDemo app = new LookAndFeelDemo();
        Component contents = app.createComponents();
        frame.getContentPane().add(contents, BorderLayout.CENTER);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event dispatch thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

8. Themes


Themes were introduced as a way of easily changing the colors and fonts of the cross-platform Java (Metal) Look and Feel. In the sample program, LookAndfeelDemo.java, listed above, you can change the theme of the Metal L&F by setting the THEME constant on line 23 to one of three values:

◉ DefaultMetal
◉ Ocean
◉ Test

Ocean, which is a bit softer than the pure Metal look, has been the default theme for the Metal (Java) L&F since Java SE 5. Despite its name, DefaultMetal is not the default theme for Metal (it was before Java SE 5, which explains its name). The Test theme is a theme defined in TestTheme.java, which you must compile with LookAndfeelDemo.java. As it is written, TestTheme.java sets the three primary colors (with somewhat bizarre results). You can modify TestTheme.java to test any colors you like.

The section of code where the theme is set is found beginning on line 92 of LookAndfeelDemo.java. Note that you must be using the Metal L&F to set a theme.

 if (LOOKANDFEEL.equals("Metal")) {
    if (THEME.equals("DefaultMetal"))
       MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
    else if (THEME.equals("Ocean"))
       MetalLookAndFeel.setCurrentTheme(new OceanTheme());
    else
       MetalLookAndFeel.setCurrentTheme(new TestTheme());
                   
    UIManager.setLookAndFeel(new MetalLookAndFeel());
 }   

9. The SwingSet2 Demonstration Program


When you download the JDK and JavaFX Demos and Samples bundle and open it up, there is a demo\jfc folder that contains a demonstration program called SwingSet2. This program has a graphically rich GUI and allows you to change the Look and Feel from the menu. Further, if you are using the Java (Metal) Look and Feel, you can choose a variety of different themes. The files for the various themes (for example, RubyTheme.java) are found in the SwingSet2\src folder.

This is the "Ocean" theme, which is the default for the cross-platform Java (Metal) Look and Feel:

Oracle Java Tutorials and Material, Oracle Java certification

This is the "Steel" theme, which was the original theme for the cross-platform Java (Metal) Look and Feel:

Oracle Java Tutorials and Material, Oracle Java certification

To run the SwingSet2 demo program on a system that has the JDK installed, use this command:

java -jar SwingSet2.jar
This will give you the default theme of Ocean.

To get the metal L&F, run this:

java -Dswing.metalTheme=steel -jar SwingSet2.jar

◈ The Synth Look and Feel

Creating a custom look and feel, or modifying an existing one, can be a daunting task. The javax.swing.plaf.synth package can be used to create a custom look and feel with much less effort. You can create a Synth look and feel either programatically or through the use of an external XML file. The discussion below is devoted to the creation of a Synth look and feel using an external XML file. Creating a Synth c programatically is discussed in the API documentation.

With the Synth look and feel, you provide the "look." Synth itself provides the "feel." Thus, you can think of the Synth L&F as a "skin."

The Synth Architecture


Recall from the previous topic that it is the responsibility of each L&F to provide a concrete implementation for each of the many ComponentUI subclasses defined by Swing. The Synth L&F takes care of this for you. To use Synth, you need not create any ComponentUIs—rather you need only specify how each component is painted, along with various properties that effect the layout and size.

Synth operates at a more granular level than a component—this granular level is called a "region." Each component has one or more regions. Many components have only one region, such as JButton. Others have multiple regions, such as JScrollBar. Each of the ComponentUIs provided by Synth associates a SynthStyle with each of the regions defined by the ComponentUI. For example, Synth defines three regions for JScrollBar: the track, the thumb and the scroll bar itself. The ScrollBarUI (the ComponentUI subclass defined for JScrollBar) implementation for Synth associates a SynthStyle with each of these regions.

Oracle Java Tutorials and Material, Oracle Java certification
SynthStyle provides style information used by the Synth ComponentUI implementation. For example, SynthStyle defines the foreground and background color, font information, and so forth. In addition, each SynthStyle has a SynthPainter that is used to paint the region. For example, SynthPainter defines the two methods paintScrollBarThumbBackground and paintScrollBarThumbBorder, which are used to paint the scroll bar thumb regions.

Each of the ComponentUIs in Synth obtain SynthStyles using a SynthStyleFactory. There are two ways to define a SynthStyleFactory: through a Synth XML file, or programatically. The following code shows how to load an XML file dictating the look of Synth—beneath the covers this creates a SynthStyleFactory implementation populated with SynthStyles from the XML file:

  SynthLookAndFeel laf = new SynthLookAndFeel();
  laf.load(MyClass.class.getResourceAsStream("laf.xml"), MyClass.class);
  UIManager.setLookAndFeel(laf);

The programmatic route involves creating an implementation of SynthStyleFactory that returns SynthStyles. The following code creates a custom SynthStyleFactory that returns distinct SynthStyles for buttons and trees:

 class MyStyleFactory extends SynthStyleFactory {
     public SynthStyle getStyle(JComponent c, Region id) {
         if (id == Region.BUTTON) {
             return buttonStyle;
         }
         else if (id == Region.TREE) {
             return treeStyle;
         }
         return defaultStyle;
     }
 }
 SynthLookAndFeel laf = new SynthLookAndFeel();
 UIManager.setLookAndFeel(laf);
 SynthLookAndFeel.setStyleFactory(new MyStyleFactory());

The XML File

An explanation of the DTD for the Synth XML file can be found at javax.swing.plaf.synth/doc-files/synthFileFormat.html.

When you load a Synth look and feel, only those GUI components (or regions) for which there is a definition (a "style" bound to the region, as discussed below) are rendered. There is no default behavior for any components—without style definitions in the Synth XML file, the GUI is a blank canvas.

To specify the rendering of a component (or region), your XML file must contain a <style> element, which is then bound to the region using the <bind> element. As an example, let's define a style that includes the font, foreground color, and background color, and then bind that style to all components. It is a good idea to include such an element in your Synth XML file while you are developing it—then, any components you haven't yet defined will at least have colors and a font:

<synth>
  <style id="basicStyle">
    <font name="Verdana" size="16"/>
    <state>
      <color value="WHITE" type="BACKGROUND"/>
      <color value="BLACK" type="FOREGROUND"/>
    </state>
  </style>
  <bind style="basicStyle" type="region" key=".*"/>
</synth>

Let's analyse this style definition:

1. The <style> element is the basic building block of the Synth XML file. It contains all the information needed to describe a region's rendering. A <style> element can describe more than one region, as is done here. In general, though, it is best to create a <style> element for each component or region. Note that the <style> element is given an identifier, the string "basicStyle." This identifier will be used later in the <bind> element.

2. The <font> element of the <style> element sets the font to Verdana, size 16.

3. The <state> element of the <style> element will be discussed below. The <state> element of a region can have one, or a mixture, of seven possible values. When the value is not specified, the definition applies to all states, which is the intention here. Therefore, the background and foreground colors "for all states" are defined in this element.

4. Finally, the <style> element with the identifier "basicStyle" that has just been defined is bound to all regions. The <bind> element binds "basicStyle" to "region" types. Which region type or types the binding applies to is given by the "key" attribute, which is ".*" in this case, the regular expression for "all."

Let's look at the pieces of the Synth XML file before creating some working examples. We'll start with the <bind> element, showing how a given <style> is applied to a component or region.

The <bind> Element

Whenever a <style> element is defined, it must be bound to one or more components or regions before it has an effect. The <bind> element is used for this purpose. It requires three attributes:

1. style is the unique identifier of a previously defined style.

2. type is either "name" or "region." If type is a name, obtain the name with the component.getName() method. If type is a region, use the appropriate constant defined in the Region class in the javax.swing.plaf.synth package.

3. key is a regular expression used to determine which components or regions the style is bound to.

A Region is a way of identifying a component or part of a component. Regions are based on the constants in the Region class, modified by stripping out underscores:

For example, to identify the SPLIT_PANE region you would use SPLITPANE, splitpane, or SplitPane (case insensitive).

When you bind a style to a region, that style will apply to all of the components with that region. You can bind a style to more than one region, and you can bind more than one style to a region. For example,

<style id="styleOne">
   <!-- styleOne definition goes here -->
</style>

<style id="styleTwo">
   <!-- styleTwo definition goes here -->
</style>

<bind style="styleOne" type="region" key="Button"/>
<bind style="styleOne" type="region" key="RadioButton"/>
<bind style="styleOne" type="region" key="ArrowButton"/>

<bind style="styleTwo" type="region" key="ArrowButton"/>

You can bind to individual, named components, whether or not they are also bound as regions. For example, suppose you want to have the "OK" and "Cancel" buttons in your GUI treated differently than all the other buttons. First, you would give the OK and Cancel buttons names, using the component.setName() method. Then, you would define three styles: one for buttons in general (region = "Button"), one for the OK button (name = "OK"), and one for the Cancel button (name = "Cancel"). Finally, you would bind these styles like this:

<bind style="styleButton" type="region" key="Button">
<bind style="styleOK" type="name" key="OK">
<bind style="styleCancel" type="name" key="Cancel">

As a result, the "OK" button is bound to both "styleButton" and "styleOK," while the "Cancel" button is bound to both "styleButton" and "styleCancel."

When a component or region is bound to more than one style, the styles are merged

Note: Just as a style can be bound to multiple regions or names, multiple styles can be bound to a region or name. These multiple styles are merged for the region or name. Precedence is given to styles defined later in the file.

The <state> Element

The <state> element allows you to define a look for a region that depends on its "state." For example, you will usually want a button that has been PRESSED to look different than the button in its ENABLED state. There are seven possible values for <state> that are defined in the Synth XML DTD. They are:

1. ENABLED
2. MOUSE_OVER
3. PRESSED
4. DISABLED
5. FOCUSED
6. SELECTED
7. DEFAULT

You can also have composite states, separated by 'and'—for example, ENABLED and FOCUSED. If you do not specify a value, the defined look will apply to all states.

As an example, here is a style that specifies painters per state. All buttons are painted a certain way, unless the state is "PRESSED," in which case they are painted differently:

<style id="buttonStyle">
  <property key="Button.textShiftOffset" type="integer" value="1"/>
  <insets top="10" left="10" right="10" bottom="10"/>

  <state>
    <imagePainter method="buttonBackground" path="images/button.png"
                         sourceInsets="10 10 10 10"/>
  </state>
  <state value="PRESSED">
    <color value="#9BC3B1" type="BACKGROUND"/>
    <imagePainter method="buttonBackground" path="images/button2.png"
                        sourceInsets="10 10 10 10"/>
  </state>
</style>
<bind style="buttonStyle" type="region" key="Button"/>

Ignoring the <property> and <insets> elements for the moment, you can see that a pressed button is painted differently than an unpressed button.

The <state> value that is used is the defined state that most closely matches the state of the region. Matching is determined by the number of values that match the state of the region. If none of the state values match, then the state with no value is used. If there are matches, the state with the most individual matches will be chosen. For example, the following code defines three states:

<state id="zero">
  <color value="RED" type="BACKGROUND"/>
</state>
<state value="SELECTED and PRESSED" id="one">
  <color value="RED" type="BACKGROUND"/>
</state>
<state value="SELECTED" id="two">
  <color value="BLUE" type="BACKGROUND"/>
</state>

If the state of the region contains at least SELECTED and PRESSED, state one will be chosen. If the state contains SELECTED, but not does not contain PRESSED, state two will be used. If the state contains neither SELECTED nor PRESSED, state zero will be used.

When the current state matches the same number of values for two state definitions, the one that is used is the first one defined in the style. For example, the MOUSE_OVER state is always true of a PRESSED button (you can't press a button unless the mouse is over it). So, if the MOUSE_OVER state is declared first, it will always be chosen over PRESSED, and any painting defined for PRESSED will not be done.

<state value="PRESSED"> 
   <imagePainter method="buttonBackground" path="images/button_press.png"
                          sourceInsets="9 10 9 10" />
   <color type="TEXT_FOREGROUND" value="#FFFFFF"/>      
</state>
      
<state value="MOUSE_OVER">    
   <imagePainter method="buttonBackground" path="images/button_on.png"
                          sourceInsets="10 10 10 10" />
   <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
</state>

The code above will work properly. However, if you reverse the order of the MOUSE_OVER and PRESSED states in the file, the PRESSED state will never be used. This is because any state that is PRESSED state is also a MOUSE_OVER state. Since the MOUSE_OVER state was defined first, it is the one that will be used.

Colors and Fonts

The <color> element requires two attributes:

value can be any one of the java.awt.Color constants, such as RED, WHITE, BLACK, BLUE, etc. It can also be a hex representation of RGB values, such as #FF00FF or #326A3B.

type describes where the color applies—it can be BACKGROUND, FOREGROUND, FOCUS, TEXT_BACKGROUND, OR TEXT_FOREGROUND.

For example:

  <style id="basicStyle">
    <state>
      <color value="WHITE" type="BACKGROUND"/>
      <color value="BLACK" type="FOREGROUND"/>
    </state>
  </style>

The <font> element has three attributes:

1. name—the name of the font. For example, Arial or Verdana.

2. size—the size of the font in pixels.

3. style (optional)—BOLD, ITALIC, OR BOLD ITALIC. If omitted, you get a normal font.

For example:

  <style id="basicStyle">
    <font name="Verdana" size="16"/>
  </style>

Each of the <color> element and the <font> element has an alternate usage. Each can have an id attribute or an idref attribute. Using the id attribute, you can define a color that you can reuse later by using the idref attribute. For example,

<color id="backColor" value="WHITE" type="BACKGROUND"/>
<font id="textFont" name="Verdana" size="16"/>
...
...
...
<color idref="backColor"/>
<font idref="textFont"/>

Insets

The insets add to the size of a component as it is drawn. For example, without insets, a button with a caption of Cancel will be just large enough to contain the caption in the chosen font. With an <insets> element like this

<insets top="15" left="20" right="20" bottom="15"/>,

the button will be made larger by 15 pixels above and below the caption and 20 pixels to the left and right of the caption.

Painting With Images

Synth's file format allows customizing the painting by way of images. Synth's image painter breaks an image into nine distinct areas: top, top right, right, bottom right, bottom, bottom left, left, top left, and center. Each of the these areas is painted into the destination. The top, left, bottom, and right edges are tiled or stretched, while the corner portions (sourceInsets) remain fixed.

Note: There is no relation between the <insets> element and the sourceInsets attribute. The <insets> element defines the space taken up by a region, while the sourceInsets attributes define how to paint an image. The <insets> and sourceInsets will often be similar, but they need not be.

You can specify whether the center area should be painted with the paintCenter attribute. The following image shows the nine areas:

Oracle Java Tutorials and Material, Oracle Java certification

Let's create a button as an example. To do this we can use the following image (shown larger than its actual size):

Oracle Java Tutorials and Material, Oracle Java certification

The red box at the upper left corner is 10 pixels square (including the box border)—it shows the corner region that should not be stretched when painting. To achieve this, the top and left sourceInsets should be set to 10. We'll use the following style and binding:

<style id="buttonStyle">
   <insets top="15" left="20" right="20" bottom="15"/>
   <state>
      <imagePainter method="buttonBackground" path="images/button.png"
        sourceInsets="10 10 10 10"/>
   </state>
</style>
<bind style="buttonStyle" type="region" key="button"/>

The lines inside the <state> element specify that the background of buttons should be painted using the image images/button.png. That path is relative to the Class that is passed into SynthLookAndFeel's load method. The sourceInsets attribute specifies the areas of the image that are not to be stretched. In this case the top, left, bottom, and right insets are each 10. This will cause the painter not to stretch a 10 x 10 pixel area at each corner of the image.

The <bind> binds buttonStyle to all buttons.

The <imagePainter> element provides all the information needed to render a portion of a region. It requires only a few attributes:

◉ method—this specifies which of the methods in the javax.swing.plaf.synth.SynthPainter class is to be used for painting. The SynthPainter class contains about 100 methods that begin with paint. When you determine which one you need, you remove the paint prefix, change the remaining first letter to lowercase, and use the result as the method attribute. For example, the SynthPainter method paintButtonBackground becomes the attribute buttonBackground.

◉ path—the path to the image to be used, relative to the Class that is passed into SynthLookAndFeel's load method.

◉ sourceInsets—the insets in pixels, representing the width and height of the corner areas that should not be stretched They map to the top, left, bottom, and right, in that order.

◉ paintCenter (optional) : This attribute lets you keep the center of an image or get rid of it (in a text field, for example, so text can be drawn).

The listing below shows the XML code for loading different images depending on the <state> of the button

  <style id="buttonStyle">
    <property key="Button.textShiftOffset" type="integer" value="1"/>
    <insets top="15" left="20" right="20" bottom="15"/>
    <state>
      <imagePainter method="buttonBackground" path="images/button.png"
                    sourceInsets="10 10 10 10"/>
    </state>
    <state value="PRESSED">
      <imagePainter method="buttonBackground" path="images/button2.png"
                    sourceInsets="10 10 10 10"/>
    </state>
  </style>
  <bind style="buttonStyle" type="region" key="button"/>

button2.png shows the depressed version of button.png, shifted one pixel to the right. The line

<property key="Button.textShiftOffset" type="integer" value="1"/>

shifts the button text accordingly, as discussed in the next section.

The <property> Element

<property> elements are used to add key value pairs to a <style> element. Many components use the key value pairs for configuring their visual appearance.

The <property> element has three attributes:

◉ key—the name of the property.

◉ type—the data type of the property.

◉ value—the value of the property.

There is a property table (componentProperties.html) that lists the properties each component supports: javax/swing/plaf/synth/doc-files/componentProperties.html.

Since the button2.png image shifts the visual button one pixel when it is depressed, we should also shift the button text. There is a button property that does this:

<property key="Button.textShiftOffset" type="integer" value="1"/>

An Example

Here is an example, using the button style defined above. The button style, plus a "backing style" with definitions of font and colors that are bound to all regions (similar to the "basicStyle" shown in the section titled "The XML File," above) are combined in buttonSkin.xmlHere is a listing of buttonSkin.xml:

<!-- Synth skin that includes an image for buttons -->
<synth>
  <!-- Style that all regions will use -->
  <style id="backingStyle">
    <!-- Make all the regions that use this skin opaque-->
    <opaque value="TRUE"/>
    <font name="Dialog" size="12"/>
    <state>
      <!-- Provide default colors -->
      <color value="#9BC3B1" type="BACKGROUND"/>
      <color value="RED" type="FOREGROUND"/>
    </state>
  </style>
  <bind style="backingStyle" type="region" key=".*"/>
  <style id="buttonStyle">
    <!-- Shift the text one pixel when pressed -->
    <property key="Button.textShiftOffset" type="integer" value="1"/>
    <insets top="15" left="20" right="20" bottom="15"/>
    <state>
      <imagePainter method="buttonBackground" path="images/button.png"
                    sourceInsets="10 10 10 10"/>
    </state>
    <state value="PRESSED">
      <imagePainter method="buttonBackground" path="images/button2.png"
                    sourceInsets="10 10 10 10"/>
    </state>
  </style>
  <!-- Bind buttonStyle to all JButtons -->
  <bind style="buttonStyle" type="region" key="button"/> 
</synth>

We can load this XML file to use the Synth look and feel for a simple application called SynthApplication.java. The GUI for this application includes a button and a label. Every time the button is clicked, the label increments.

Note: The label is painted, even though buttonSkin.xml does not contain a style for it. This is because there is a general "backingStyle" that includes a font and colors.

Painting With Icons

Radio buttons and check boxes typically render their state by fixed-size icons. For these, you can create an icon and bind it to the appropriate property (refer to the properties table, javax/swing/plaf/synth/doc-files/componentProperties.html). For example, to paint radio buttons that are selected or unselected, use this code:

<style id="radioButton">
   <imageIcon id="radio_off" path="images/radio_button_off.png"/>
   <imageIcon id="radio_on" path="images/radio_button_on.png"/>
   <property key="RadioButton.icon" value="radio_off"/>
   <state value="SELECTED">   
      <property key="RadioButton.icon" value="radio_on"/>
   </state>
</style>
<bind style="radioButton" type="region" key="RadioButton"/>        

Custom Painters

Synth's file format allows for embedding arbitrary objects by way of the long-term persistence of JavaBeans components . This ability is particularly useful in providing your own painters beyond the image-based ones Synth provides. For example, the following XML code specifies that a gradient should be rendered in the background of text fields:

<synth>
  <object id="gradient" class="GradientPainter"/>
  <style id="textfield">
    <painter method="textFieldBackground" idref="gradient"/>
  </style>
  <bind style="textfield" type="region" key="textfield"/>
</synth>
Where the GradientPainter class looks like this:

public class GradientPainter extends SynthPainter {
   public void paintTextFieldBackground(SynthContext context,
                                        Graphics g, int x, int y,
                                        int w, int h) {
      // For simplicity this always recreates the GradientPaint. In a
      // real app you should cache this to avoid garbage.
      Graphics2D g2 = (Graphics2D)g;
      g2.setPaint(new GradientPaint((float)x, (float)y, Color.WHITE,
                 (float)(x + w), (float)(y + h), Color.RED));
      g2.fillRect(x, y, w, h);
      g2.setPaint(null);
   }
}

◈ The Nimbus Look and Feel

Nimbus is a polished cross-platform look and feel introduced in the Java SE 6 Update 10 (6u10) release. The following screen capture, from SwingSet3 shows the Nimbus look and feel.

Oracle Java Tutorials and Material, Oracle Java certification

Nimbus uses Java 2D vector graphics to draw the user interface (UI), rather than static bitmaps, so the UI can be crisply rendered at any resolution.

Nimbus is highly customizable. You can use the Nimbus look and feel as is, or you can skin (customize) the look with your own brand.

Enabling the Nimbus Look and Feel

For backwards compatibility, Metal is still the default Swing look and feel, but you can change to Nimbus in one of three ways:

◉ Add the following code to the event-dispatching thread before creating the graphical user interface (GUI):
import javax.swing.UIManager.*;

try {
    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
            UIManager.setLookAndFeel(info.getClassName());
            break;
        }
    }
} catch (Exception e) {
    // If Nimbus is not available, you can set the GUI to another look and feel.
}

The first line of code retrieves the list of all installed look and feel implementations for the platform and then iterates through the list to determine if Nimbus is available. If so, Nimbus is set as the look and feel.

Version Note: Do not set the Nimbus look and feel explicitly by invoking the UIManager.setLookAndFeel method because not all versions or implementations of Java SE 6 support Nimbus. Additionally, the location of the Nimbus package changed between the JDK 6 Update 10 and JDK 7 releases. Iterating through all installed look and feel implementations is a more robust approach because if Nimbus is not available, the default look and feel is used. For the JDK 6 Update 10 release, the Nimbus package is located at com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel.

◉ Specify Nimbus as the default look and feel for a particular application at the command line, as follows:

java -Dswing.defaultlaf=javax.swing.plaf.nimbus.NimbusLookAndFeel MyApp

◉ Permanently set the default look and feel to Nimbus by adding the following line to the <JAVA_HOME>/lib/swing.properties file:
swing.defaultlaf=javax.swing.plaf.nimbus.NimbusLookAndFeel

If the swing.properties file does not yet exist, you need to create it.

«« Previous
Next »»