Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

The Swing Package, A Preview of Pluggable Look and Feel

Java Programming, Lecture Notes # 111, Revised 03/25/98.

Preface

Beginning with the Summer 1998 session, students in Prof. Baldwin's Intermediate Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

This lesson was originally written in March of 1998 using the software and documentation in the JDK 1.1.3, JFC 1.1, and Swing 1.0.1 download packages. Swing 1.0.1 was contained in JFC 1.1.

The 1.0.1 version of Swing is designed to be compatible with JDK 1.1.  A new version of Swing is scheduled to be incorporated in JDK 1.2.  Hopefully there won't be too many changes and the material in this lesson will continue to be relevant.
 

Introduction

Several previous lessons have provided information regarding the handling of events in JDK 1.1.  A previous lesson provided an overview of the Java Abstract Windows Toolkit (AWT).  The AWT is used in Java to create Graphical User Interfaces (GUI).

This lesson provides a preview of an alternative to the AWT for creating GUIs - Swing.

Swing provides a set of lightweight components that can be used in place of the components in the AWT, sometimes to advantage.  (See other lessons for a discussion of the difference between lightweight and heavyweight components.)  In addition, Swing provides a number of components that are not contained in the AWT (progress bars, tool tips, trees, combo boxes, etc.).

At one level, the Swing components can be viewed simply as a replacement for the AWT components.  At another level, however, the Swing components are much more sophisticated than the AWT components for a variety of reasons.  For example, additional event structures are available with the Swing components and it is possible to change the "look and feel" of the GUI at runtime.

Perhaps even more important, the Swing components are designed using a Model-View-Control (MVC) concept which makes them significantly more powerful.  A lesson on MVC is provided later.  In brief, MVC is a concept where data is provided by one or more controls, maintained by a model, and displayed by one or more views.  MVC is an OOD design pattern that is strongly recommended for the design of robust systems.  With MVC, either the control or the view can be modified without the attendant requirement to modify the model.

One important manifestation of MVC in the Swing components is that you can make them look like anything you please.  This is referred to as Pluggable Look and Feel (PLAF)Swing 1.0.1 comes with three different PLAF implementations right out of the box:

Apparently a Mac look and feel will also be available soon.  In addition, you can create your own look and feel (L&F) if you so choose, but the programming effort may not be trivial.

Therefore, if you create a GUI using exclusively Swing components, you can provide your customer with the ability to switch between L&F implementations at runtime.  A sample program will be provided later in this lesson that demonstrates this capability.

When you download Swing 1.0.1, a demonstration program named SwingSet comes with the download that illustrates most of the available Swing components.  This program also allows you to switch the L&F at runtime.

If you run that program, you will see another important difference between Swing and the AWT.  In particular, most of the Swing components are containers.  This means that they can contain other components and can also contain images.  Therefore, creation of GUI components containing images (such as button bars with icons) is not too difficult with Swing.

If we were to start from scratch, we could write an entire book on Swing.  However, much of the information in that book would duplicate material already provided in discussions of the AWT.  Therefore, the plan at this time is to provide a series of lessons that highlight the differences between Swing and the AWT, under the assumption that you already understand the AWT.

Sometimes when you change the L&F of a GUI, you can be surprised by the result.  Therefore, it is important to have the ability to test your GUI for different L&F implementations.  For that reason, we will begin our investigation into Swing by providing a program that makes it easy to perform such testing.
 

Pluggable Look and Feel Panel -- Sample Program

The purpose of this class is to construct an object that can easily be associated with a GUI to test that GUI for all of the L&F implementations installed with the current JDK.

To associate this object with a GUI under test, simply pass a reference to the JFrame containing the GUI as a parameter when this object is constructed. Typically, you will instantiate an object of this class and add it to a JFrame object that is separate from the GUI under test. The only link between the two will be that the object of this class has a reference to the GUI under test so that it can change the L&F of that GUI.

This class creates a JPanel. The JPanel contains one JButton for each L&F implementation in the currently installed JDK. Clicking each JButton will cause the L&F of the GUI under test to change to the L&F represented by that JButton.  (The L&F of the object of this class also changes as well.)

The name of the L&F is displayed on the JButton.

This class will be used with a variety of programs in this tutorial in order to test them against the currently installed look and feel implementations.

The key statements in selecting and then implementing the new L&F are:
 
  UIManager.setLookAndFeel(plafClassName);  
  
  SwingUtilities.updateComponentTreeUI(thisPlafPanel); 
  SwingUtilities.updateComponentTreeUI(testGui);
These statements will be discussed further as we discuss the programs in this lesson.
 
This class was tested using JDK 1.1.3 and Swing 1.0.1 under Win95.
 

Pluggable Look and Feel Panel  -- Interesting Code Fragments

The first interesting code fragment shows the import statements for this program.  This is provided simply to remind you that you will need to deal with the swing package when working with Pluggable Look and Feel (and other aspects of Swing as well).
 
import java.awt.event.*;
import com.sun.java.swing.*;
import java.util.*;
 
The next fragment is the declaration of an unusual reference variable type that is used to refer to an array of L&F information.  As of Swing 1.0.1, the document that should explain this type is missing from the download documentation file.

The best available information seems to be the following statement that was extracted from the description of the method named getInstalledLookAndFeels() that returns an array object of this type (a minor typo was corrected by the author in this quotation):
 
"Return an array of objects that provide some information about the LookAndFeel implementations that have been installed with this java development kit.  The LookAndFeelInfo objects can be used by an application to construct a menu of look and feel options for the user or to set the look and feel at start up time." 
 
The reference variable declaration follows.
 
public class PlafPanel01 extends JPanel {

  UIManager.LookAndFeelInfo[] plafInfoArray;
 
In order for the event handlers in an object of this class to have the ability to control the L&F of a test GUI, the object must have a reference to that test GUI.  Such a reference is passed in as a parameter to the constructor.

Also, in order for the event handler to control the L&F of an object of this class, it must have a reference to the object.  The next fragment shows two variables that are used to save these two required references.
 
  JFrame testGui;//save a reference to the test GUI here
  PlafPanel01 thisPlafPanel = this;//ref to this object
 
The next fragment shows the beginning of the constructor for an object of this type.  The first two statements in the object

 
  
  public PlafPanel01(JFrame testGui) {//constructor
    this.testGui = testGui;//save ref to test GUI
    this.add(new JLabel(//label the PlafPanel
       "PL&F Selection Panel, Copyright 1998, RGBaldwin"));
 
That gets us through the preliminaries and into the meat of the subject.  The next fragment gets an array containing a list of all of the L&F implementations contained in the current JDK installation.  The method used in this fragment was discussed earlier.
 
    plafInfoArray = UIManager.getInstalledLookAndFeels();
 
Once we have the list of L&F implementations, we need to create a JButton object that will be used to invoke each of those implementations.  Since we don't know how many such implementations may be installed in the currently installed JDK, we will use a Vector object to contain references to those JButton objects.  We instantiate the JButton objects in a for loop which makes one iteration for each element in the array of L&F implementations.

The declaration of the Vector and the beginning of the for loop with a statement to populate the Vector are shown in the next code fragment.
 
    Vector theButtons = new Vector(plafInfoArray.length);
    
    for(int cnt = 0; cnt < plafInfoArray.length; cnt++){
      theButtons.addElement(new JButton());
 
We are going to need the name of the L&F class associated with each of the elements in the L&F array for a couple of reasons, so the next code fragment extracts the name and saves it in a reference variable named theClassName.
 
//Still in the for loop      
  String theClassName = plafInfoArray[cnt].getClassName();
 
One of the uses for the name of the L&F class is to create labels for each of the JButton objects used to invoke the new L&F.  The entire class name string is quite long.  We will extract a short name for each specific L&F implementation and use it to label the JButton  corresponding to that L&F. The short name that we will use appears following the last period in the String representation of the class name for the L&F implementation, so we will use some simple string manipulation to extract the name..

Note the requirement to downcast the reference extracted from the Vector.
 
//Still in the for loop      
      String label = theClassName.substring(
                          theClassName.lastIndexOf(".")+1);
      ((JButton)theButtons.elementAt(cnt)).setText(label);
 
Following this, we instantiate and register an Action Listener object on each of the JButton objects.  When the button is clicked, the code in the actionPerformed() method of the listener object causes the L&F to be changed to the specific L&F represented by the button.

Note that because the references to the buttons are stored as type Object in a Vector, it is necessary to downcast them before they can be used. The name of the L&F class is also needed by the Action Event handler, so we pass it as a parameter to the constructor for the listener object.
 
      ((JButton)theButtons.elementAt(cnt)).
          addActionListener(new MyActionListener(
                                            theClassName));
 
Finally, while still in the for loop, we add each JButton to the JPanel.  That ends the for loop and ends the constructor.
 
//Still in the for loop      
      //Add each JButton to the JPanel.
      add((JButton)theButtons.elementAt(cnt));
    }//end for loop
  }//end constructor
 
The next code fragment shows the beginning of an inner class used to instantiate the Action Listener objects.  Note that although this is an inner class, it is not an anonymous inner class.

The following code fragment shows the constructor for the listener object which saves the name of the L&F class associated with the button on which the listener is registered.
 
  class MyActionListener implements ActionListener{
    String plafClassName;//save name of plaf class here

    MyActionListener(String plafClassName){
      this.plafClassName = plafClassName;
    }//end constructor
 
The actionPerformed() method is the heart of all Action Listener objects.  The next code fragment shows the beginning of this method  which invokes the static setLookAndFeel() method of the UIManager class to set the current default L&F.  This method throws several different types of exceptions so it in enclosed in a try/catch block.
 
    public void actionPerformed(ActionEvent e){
      //Set the current default L&F to that passed in as
      // a parameter
      try{
        UIManager.setLookAndFeel(plafClassName);
      }catch(Exception ex){System.out.println(ex);}
 
Simply setting the current default L&F doesn't cause it to become effective.  Rather, it is necessary to take specific action to cause the new L&F to take effect.

The following code fragment accomplishes this by invoking the static updateComponentTreeUI() method on two different references:

The description of this method, as extracted from the Swing documentation is as follows:
 
"A simple minded look and feel change: ask each  node in the tree to updateUI(), i.e. to  initialize its UI property with the current look and feel.
The following code will cause both the test GUI and the panel containing the L&F buttons to reflect the new L&F selected by clicking on one of the buttons.
 
      //Set the L&F for this PlafPanel01 object
      SwingUtilities.updateComponentTreeUI(thisPlafPanel);
      //Set the L&F for the test GUI object
      SwingUtilities.updateComponentTreeUI(testGui);
                                    
    }//end actionPerformed()
  }//end class MyActionListener
}//end class PlafPanel01
 
That is the end of the definition of the actionPerformed() method, the end of the Action Listener class, and the end of the PlafPanel01 class.

A complete listing of this program is contained in the next section.

The program discussed in the sections sections following the program listing put this class to work by using it to invoke different L&F implementations on a simple GUI consisting of a JButton and a JTextField.
 

Pluggable Look and Feel Panel -- Program Listing

This section contains a complete listing of the class used to display different L&F implementations.
 
/*File PlafPanel01 Copyright 1998, R.G.Baldwin
  The purpose of this class is to construct an object that
  can easily be associated with a GUI to test the GUI
  for all of the Look and Feel implementations installed 
  with the current JDK.
  
  To associate this object with a GUI under test, pass
  a reference to the JFrame containing the GUI as a
  parameter when this object is constructed. Typically,
  you will instantiate an object of this class and add
  it to a JFrame object that is separate from the GUI
  under test.
  
  This class creates a JPanel.  The JPanel contains one 
  JButton for each L&F implementation in the currently 
  installed JDK.  Clicking each JButton will cause the L&F
  of the GUI to change to the L&F represented by that 
  JButton.

  The name of the L&F is displayed on the JButton.
  
  This class will be used with a variety of programs in
  this tutorial in order to test them against the currently
  installed look and feel implementations.
  
  The key statements in selecting and then implementing
  the new L&F are:
  
  UIManager.setLookAndFeel(plafClassName);  
  
  SwingUtilities.updateComponentTreeUI(thisPlafPanel);
  SwingUtilities.updateComponentTreeUI(testGui);
                           
  These statements are discussed further in the comments
  in the program.
                           
  This class was tested using JDK 1.1.3 and Swing 1.0.1 
  under Win95.
**********************************************************/

import java.awt.event.*;
import com.sun.java.swing.*;
import java.util.*;

public class PlafPanel01 extends JPanel {
  /*
  The following is an unusual reference variable type that
  is used to refer to an array of L&F information.  As of
  Swing 1.0.1, the document that should explain this type
  is missing from the download documentation file.
  
  The best available information seems to be the following
  statement that was extracted from the description of the
  method named getInstalledLookAndFeels() that returns an 
  array object of this type (a minor typo was corrected
  by the author in this quotation):

  "Return an array of objects that provide some 
  information about the LookAndFeel implementations
  that have been installed with this java development kit.
  The LookAndFeelInfo objects can be used by an 
  application to construct a menu of look and feel options
  for the user or to set the look and feel at start up
  time."
  */
  UIManager.LookAndFeelInfo[] plafInfoArray;
  //-----------------------------------------------------//
  JFrame testGui;//save a reference to the test GUI here
  PlafPanel01 thisPlafPanel = this;//ref to this object
  
  public PlafPanel01(JFrame testGui) {//constructor
    this.testGui = testGui;//save ref to test GUI
    this.add(new JLabel(//label the PlafPanel
       "PL&F Selection Panel, Copyright 1998, RGBaldwin"));
  
    //Get the list of L&Fs installed with the current JDK
    //See note above regarding this method.
    plafInfoArray = UIManager.getInstalledLookAndFeels();
 
    //Create a vector of references to JButton objects 
    // with one element in the vector for each L&F
    // implementation in the current JDK. 
    Vector theButtons = new Vector(plafInfoArray.length);
    
    //Create one JButton object for each L&F implementation
    // and put its reference in the Vector.
    for(int cnt = 0; cnt < plafInfoArray.length; cnt++){
      theButtons.addElement(new JButton());
      
      //Get the name of the class for each specific L&F
      // implementation
      String theClassName = 
                         plafInfoArray[cnt].getClassName();
      
      //Extract a short name for each specific L&F 
      // implementation and use it to label the JButton 
      // corresponding to that L&F. The short name appears
      // following the last period in the String
      // representation of the class name for the 
      // L&F implementation. Note the requirement to
      // downcast the reference extracted from the Vector.
      String label = theClassName.substring(
                          theClassName.lastIndexOf(".")+1);
      ((JButton)theButtons.elementAt(cnt)).setText(label);
      
      //Add an action listener to each JButton that will
      // cause the L&F to change to the one represented by
      // that JButton whenever the JButton is clicked. Note
      // that because the references to the buttons are
      // stored in a Vector, it is necessary to downcast
      // them from Object to JButton.
      ((JButton)theButtons.elementAt(cnt)).
          addActionListener(new MyActionListener(
                                            theClassName));
      
      //Add each JButton to the JPanel.
      add((JButton)theButtons.elementAt(cnt));
    }//end for loop
  }//end constructor
  //=====================================================//
  
  //Inner class for action listeners
  class MyActionListener implements ActionListener{
    String plafClassName;//save name of plaf class here

    //Constructor
    MyActionListener(String plafClassName){
      //save the incoming parameter
      this.plafClassName = plafClassName;
    }//end constructor
    
    public void actionPerformed(ActionEvent e){
      //Set the current default L&F to that passed in as
      // a parameter
      try{
        UIManager.setLookAndFeel(plafClassName);
      }catch(Exception ex){System.out.println(ex);}
      
      //Now implement the current default L&F to make it
      // take effect. The description of the 
      // updateComponentTreeUI() method as extracted from
      // the documentation is as follows:
        
      //"A simple minded look and feel change: ask each 
      // node in the tree to updateUI(), i.e. to 
      // initialize its UI property with the current look
      // and feel."
      
      //Set the L&F for this PlafPanel01 object
      SwingUtilities.updateComponentTreeUI(thisPlafPanel);
      //Set the L&F for the test GUI object
      SwingUtilities.updateComponentTreeUI(testGui);
                                    
    }//end actionPerformed()
  }//end class MyActionListener
}//end class PlafPanel01
//=======================================================//
.

Test Program

The primary purpose of this program is to demonstrate pluggable look and feel (PL&F) and the ability to change the L&F at runtime.

It also illustrates a couple of requirements of using Swing components that differ from using AWT components.

The program creates a GUI consisting of a PlafPanel01 object in a JFrame which is used to control the L&F of the program. The PlafPanel01 object is instantiated from the custom class named PlafPanel01 and contains one JButton for each L&F implementation in the currently installed JDK.

The name of the L&F is displayed on each JButton on the PlafPanel.

The PlafPanel01 is added to the content pane of a JFrame.  Clicking each JButton on the PlafPanel01 will cause the L&F of the test GUI and the L&F of the PlafPanel01 to change to the L&F represented by that JButton.

The program also creates another GUI in a JFrame which is intended to simulate a GUI under test.  This GUI contains an inactive JButton and an inactive JTextField.  These components are provided so that you can see how they behave when the L&F is changed.
 
This program was tested using JDK 1.1.3 and Swing 1.0.1 under Windows 95

If you compile and run this program, you should see two JFrame objects on your screen.  The one at the upper left is the GUI under test and contains an inactive JButton object and an inactive JTextField object.  They were placed there so that you can see how they look and behave when you change among the available L&F implementations on your system.

The JFrame object below that one should contain one JButton object for each of the L&F implementations in your currently installed JDK.  (If your system contains a large number of L&F implementations, some of the buttons may not be visible and you may need to stretch the JFrame manually.) Each of these buttons should be labeled with the name of the L&F to which it applies.  Clicking on these buttons should cause the L&F of both JFrame objects to change to the specific L&F identified by the button.
 

Test Program -- Interesting Code Fragments

This program begins by subclassing JFrame to create a window for the test GUI.  The constructor for this object establishes the title, sets the layout to FlowLayout, adds an inactive JButton object, and adds an inactive JTextField object.

About the only thing that is new in the following code is the requirement to use the getContentPane() method for adding components to the JFrame object.  This is one of the major changes between an AWT Frame object and a Swing JFrame object.  A JFrame object is composed of different layers or panes which will be discussed in a subsequent lesson and you must add new components to the content pane..
 
public class Swing04 extends JFrame {//controlling class

  Swing04(String title) {//constructor
    // Set the title of the JFrame
    super(title);

    //Now create the test GUI for this application.
    getContentPane().setLayout(new FlowLayout());
    
    //Add some components to be viewed with the new L&F
    getContentPane().add(new JButton("Dummy JButton"));
    getContentPane().add(new JTextField("Dummy JTextField"));

  }//end constructor
As is always the case, this application requires a main() method.  In this case, the main() method instantiates an object of its own type to serve as a test GUI.  It also instantiates another object of the JFrame type containing a PlafPanel01 to serve as the control for changing the L&F.

The main method also uses an anonymous inner class to create a Window Listener object for terminating the program when the user presses the close button on the JFrame.

The following code fragment shows the instantiation of the test GUI object and the instantiation of the Window Listener object.  There is really nothing new in this code fragment relative to AWT code that we have seen before.
 
  public static void main(String args[]) {
    JFrame jFrame = new Swing04(
              "Pluggable L&F, Copyright 1998, RG Baldwin");
              
    // Set the size of the JFrame and show it
    jFrame.setSize(400,100);
    jFrame.setVisible(true);
    
    // Create a WindowAdapter to terminate the program
    // when the window is closed.
    jFrame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }//end windowClosing()
    });//end WindowListener
Finally, the following code fragment instantiates a JFrame object and adds a PlafPanel01 object to it.  Again, the getContentPane() method is used when adding a component to the JFrame object.

It is important to note that a reference to the test GUI object (jFrame) is passed to the constructor of the PlafPanel01 object when it is instantiated.  It is this reference that makes it possible for the event handlers in the object to control the L&F of the test GUI object.

Other than the required use of getContentPane(), there is nothing in this code fragment that we haven't seen many times in our work with the AWT.
 
//Still in main()
    JFrame myPlafPanel = new JFrame(
                   "PL&F Panel,Copyright 1998,RG Baldwin");
    myPlafPanel.getContentPane().add(
                                  new PlafPanel01(jFrame));
    myPlafPanel.setBounds(0,400,400,150);
    myPlafPanel.setVisible(true);

  }//end main()   
}//end class Swing04
.

Test Program -- Program Listing

A complete listing of the test program follows.
 
/*File Swing04 Copyright 1998, R.G.Baldwin
The primary purpose of this program is to demonstrate
pluggable look and feel (PL&F) and the ability to change
the L&F at runtime.

It also illustrates a couple of requirements of using
Swing components that differ from using AWT components.

The program creates a GUI in a JFrame containing a 
PlafPanel.  The PlafPanel01 is instantiated from a custom 
class named PlafPanel01 and contains one JButton for each 
L&F implementation in the currently installed JDK.  

The name of the L&F is displayed on each JButton on the
PlafPanel01.

The PlafPanel01 is added to the content pane of the JFrame.
Clicking each JButton on the PlafPanel01 will cause the L&F
of the GUI to change to the L&F represented by that 
JButton.

The program also creates another GUI in a JFrame which is
intended to simulate a GUI under test.  This GUI contains
an inactive JButton and an inactive JTextField.  These
components are provided so that you can see how then behave
when the L&F is changed.

It is instructive to see how shapes and colors for these
inactive components change as each different L&F is 
implemented.
 
Tested using JDK 1.1.3 and Swing 1.0.1 under Windows 95

*********************************************************/

import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import java.util.*;

// Subclass JFrame to display a window
public class Swing04 extends JFrame {//controlling class

  Swing04(String title) {//constructor
    // Set the title of the JFrame
    super(title);

    //Now create the test GUI for this application.
    getContentPane().setLayout(new FlowLayout());
    
    //Add some components to be viewed with the new L&F
    getContentPane().add(new JButton("Dummy JButton"));
    getContentPane().add(new JTextField("Dummy JTextField"));

  }//end constructor
  //-----------------------------------------------------//
      
  // Create main method to execute the application
  public static void main(String args[]) {
    JFrame jFrame = new Swing04(
              "Pluggable L&F, Copyright 1998, RG Baldwin");
              
    // Set the size of the JFrame and show it
    jFrame.setSize(400,100);
    jFrame.setVisible(true);
    
    // Create a WindowAdapter to terminate the program
    // when the window is closed.
    jFrame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }//end windowClosing()
    });//end WindowListener

    //Create a PlafPanel in a floating JFrame that can be
    // used to test this GUI.
    JFrame myPlafPanel = new JFrame(
                   "PL&F Panel,Copyright 1998,RG Baldwin");
    myPlafPanel.getContentPane().add(
                                  new PlafPanel01(jFrame));
    myPlafPanel.setBounds(0,400,400,150);
    myPlafPanel.setVisible(true);

  }//end main()   
}//end class Swing04
//=======================================================//
.

Review

As of this writing, no review material is available for this lesson.

-end-