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

Swing, Hidden Buttons with Icons, Icon Images, Borders, Tool Tips, Nested Buttons, and Other Fun Stuff


Java Programming, Lecture Notes # 124, Revised 08/26/98.


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.
 

Introduction

As indicated in the title, this lesson deals with a lot of fun stuff in Swing.  The lesson was originally written using JDK 1.1.6 and Swing 1.0.1.  Much that you will see in this lesson is not documented in Swing 1.0.1 (at least if it is documented, I was unable to find it).  Rather, much of what you see here was created by examining the source code for the examples that came with the Swing download and inferring the behavior of various methods on the basis of that source code.

What is a hidden button?  If you use Netscape Communicator 4.x, then you are familiar with the type of buttons that I refer to as hidden buttons.

For example, the "Back" button in the Netscape browser in Communicator 4.x is a button of this type.  Its normal appearance is to display the word Back and a little green icon but there are no borders to cause it to look like a button.  However, when you touch it with the mouse, it suddenly displays borders that make it look like a button. The new capability of Swing to deal with borders makes it possible to create buttons that behave like this in Java.

Swing also makes it possible to place images on buttons (or on just about any Swing component for that matter).  We will use ImageIcon to create images to place on our buttons.

Not only will we have icons on the buttons, they will change as we move the mouse in the vicinity of the buttons.  This is the result of a new capability in Swing  referred to as rollover effects.

You have all seen tool tips in various applications.  Swing supports tool tips and we will demonstrate that capability in this lesson as well.

As you are already aware from a previous lesson, Swing supports Pluggable Look and Feel (PL&F).  Also, as you are already aware from a previous lesson, Swing makes it possible for components to contain other components.  Just for fun, we will build a pyramid of nested JButton components and observe them for different PL&F implementations.
 
 

Sample Program

The primary purpose of this program is to demonstrate "Hidden Buttons".  These are buttons which don't look like buttons until you touch them with the mouse but which "come alive" when you touch them.

By coming alive, they take on the appearance of a button according to the current look and feel.  This behavior is implemented using the new border capability of Swing. Also, the icon on the button changes to a different image when you touch the button with the mouse. This illustrates the new ImageIcon capability of Swing and several related behaviors (such as rollover effects).

A PL&F control panel is provided so that you can change the L&F during runtime.  Thus, the program also illustrates the appearance of Hidden Buttons under different look and feel implementations.

As mentioned earlier, the program also illustrates the use of tooltips.

And just for fun, the program illustrates that every Swing component is a container that can contain other components.  This capability is illustrated by building a pyramid of nested JButton objects where each object contains the objects above it.

It is very interesting to observe the appearance of this pyramid under different L&F implementations. For example, under the JavaSoft Metal L&F, it doesn't have a three-dimensional pyramid look.  However, under the Windows and Motif L&F, the three-dimensional appearance is very pronounced.

This program requires that several gif image files be placed in the directory that contains the class files. Otherwise, you won't be able to compile and run the program.

It doesn't matter what images the gif files contain.  Of course, to fit in the allotted space, they should be small images.  To be directly compatible with this program, the names of the gif files must be bulb1, bulb2, and bulb3. Obviously, you could modify the program to accommodate gif files with other names as well.

These gif files are used to change the icons on the buttons to illustrate the different states of the buttons.

When the program starts, two Hidden Buttons and a pyramid of nested JButton objects are displayed in a JFrame object on the screen.

In addition, a separate JFrame object is displayed containing buttons that can be used to select any of the different L&F implementations currently installed on the system. When you select an L&F implementation on this JFrame, it is applied to all of the components on both JFrame objects.

One of the Hidden Buttons is a JButton.  The other is a JToggleButton.  They are identified by the text on the buttons as well as the tool tip that appears when you cause the mouse pointer to hover over a button for a short period of time.

In my implementation, using my three gif files, the normal state of the buttons is to display a light bulb that is not illuminated.

When you touch the button with the mouse (rollover), the borders appear and the light bulb image changes to one that is illuminated.

When you press the button, the light bulb changes to one that is blue and it remains blue until you release the mouse button. (See a later comment regarding the different rollover behavior of the JToggleButton when it is selected.)

Of course, your version will be somewhat different, depending on the three images that you select.

The buttons don't do anything when you click them because no action listeners were registered on them.

This program illustrates the JToggleButton which is new to Swing.

Whereas a normal JButton object responds and returns to its original appearance when you click it, a JToggleButton responds but does not return to its original appearance.  In other words, clicking on a JToggleButton causes it to toggle between two different appearances (and two different readable states as well).

Although it isn't demonstrated in this program, the two states of a JToggleButton are true and false.  Your program can determine the state by invoking the isSelected() method on the button. The change in appearance between being selected and not selected depends on the L&F in effect at the time.

Note that unlike the JButton object, the JToggleButton object doesn't change to the rolloverIcon when it is in the selected state.  It displays the normalIcon instead. However, like the JButton, it does display the rolloverIcon when it is touched by the mouse pointer but is not selected.

This program was tested using JDK 1.1.6 and Swing 1.0.1 under Windows 95
 
 

Interesting Code Fragments

We will begin with the opening line of the controlling class and the main() method.  As you can see, the controlling class extends JFrame so it is a JFrame object.

The main() method instantiates two objects.  The first is an object of the type of the controlling class.  The second is an object of the class named PlafPanel02.

You should already be familiar with the concept involved with this second object.  It was explained in an earlier lesson on Pluggable Look & Feel.  This version of the PlafPanel class differs from the one used in that lesson only to the extent that this is a totally stand-alone control panel for selecting the L&F of the associated GUI. A complete listing of the source code for PlafPanel02 is provided at the end of this lesson.

A GUI is associated with this L&F control panel by passing a reference to the GUI when the PlafPanel02 object is instantiated.  From that point forward, selecting a new L&F on the control panel will cause the L&F of the associated GUI to change to the selected L&F (the L&F of the control panel also changes as well).

The GUI named demoFrame is passed to the constructor for PlafPanel02 to associate the GUI with the L&F control panel in this program.
 
 
public class Swing09 extends JFrame {

  public static void main(String args[]) {
    Swing09 demoFrame = new Swing09();
    PlafPanel02 plafPanel = new PlafPanel02(demoFrame);
  }//end main()

That brings us to the constructor for the primary GUI for this program. We begin with the preliminaries by creating a title, setting the layout, and adding a JLabel object that contains some explanatory information.
 
  Swing09() {//constructor
    this.setTitle("Copyright 1998, RG Baldwin");
    this.getContentPane().setLayout(new FlowLayout());
    this.getContentPane().add(new JLabel(
       "Hidden Button Demo, Copyright 1998, R.G.Baldwin"));

 The next fragment instantiates a JPanel object containing the Hidden Buttons and adds the panel to the JFrame object that is the GUI. We will examine the class from which is panel is constructed shortly.
 
    this.getContentPane().add(new HiddenButtonPanel());

Next, just for fun, we build a pyramid of nested JButton objects so that we can observe their appearance under the different L&F implementations and set a tool tip on the top button..

Following this, we set the size of the GUI and make it visible.

Then we add an anonymous inner-class WindowListener to terminate the program when the user closes the JFrame object.

And that ends the constructor for the controlling class and also ends the controlling class definition.
 
    JButton a = new JButton();
    JButton b = new JButton();
    JButton c = new JButton();
    JButton d = new JButton();
    JButton e = new JButton();
    JButton f = new JButton("Top");
    f.setToolTipText("Nested JButton objects");
    this.getContentPane().add(a);
    a.add(b);
    b.add(c);
    c.add(d);
    d.add(e);
    e.add(f);

    //Set size and display the GUI  
    this.setSize(400,280);
    this.setVisible(true);
    
    // Create an inner class WindowAdapter to terminate the
    // program when the JFrame is closed.
    this.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);}});//end WindowListener
    
  }//end constructor  
}//end class Swing09

That brings us to the definition of the class that is used to instantiate a panel containing the two Hidden Buttons.

We begin by instantiating three different objects of type ImageIcon.  The ImageIcon class implements the Icon interface, so these objects can be used anywhere an object of type Icon is required.

In our case, we will need to provide a reference to an object of type Icon in three different statements associated with each of our Hidden Buttons.  We will discuss those statements later.

There are several different constructors that can be used to instantiate an object of type ImageIcon.  We are using a version of the constructor that requires a String specifying the name of the file containing the image.

This fragment also declares reference variables for our two Hidden Button objects.
 
class HiddenButtonPanel extends JPanel{

  ImageIcon normalIcon = new ImageIcon("bulb3.gif");
  ImageIcon rolloverIcon = new ImageIcon("bulb2.gif");
  ImageIcon pressedIcon = new ImageIcon("bulb1.gif");

  JToggleButton myJToggleButton;
  JButton myJButton;

That brings us to the constructor for our HiddenButtonPanel class. There is a lot of new material contained in this constructor.

This constructor instantiates two different Hidden Buttons;

Other than their type, the code to instantiate and prepare them for use is identical.  Therefore, this fragment will deal only with the JButton object.  You can find the code for the JToggleButton object in the complete listing of the program that follows later.

We begin by instantiating a JButton object using a constructor that allows us to specify a reference to an Icon image object that will appear on the button.  The ability to put images on a button is new to Swing.  This capability does not exist in the AWT.  In this case, we specify one of the ImageIcon objects instantiated earlier (normalIcon).

Next, we invoke the setRolloverIcon() method to specify an image that will automatically appear on the button when we touch the button with the mouse pointer.  Again, we provide a reference to one of the ImageIcon objects instantiated earlier (rolloverIcon),.

After this, we invoke the setPressedIcon() method to specify an image that will automatically appear on the button while it is pressed, passing one of our ImageIcon objects as a parameter (pressedIcon).

These are all new capabilities in Swing that do not exist in the AWT.

Finally, as the last action in this fragment, we invoke the setRolloverEnabled() method on the button to "enable the rollover effects."

As mentioned earlier, I was unable to find any explanatory information in the documentation for Swing 1.0.1 regarding the "rollover effects.".  Everything here was inferred from the very cryptic statements in the API documentation and an examination of the example programs that came with the Swing download.
 
  HiddenButtonPanel(){//constructor

    myJButton = new JButton("JButton", normalIcon);
    myJButton.setRolloverIcon(rolloverIcon);
    myJButton.setPressedIcon(pressedIcon);
    myJButton.setRolloverEnabled(true);

Tool Tips are amazingly easy to create with Swing.  The first statement in the next fragment is all that is required to associate a tool tip with an object.

After setting the text for the tool tip on the button by invoking the setToolTipText() method, we cause the borders on the button to be invisible by invoking setBorderPainted() and passing false as a parameter.

Until this is changed, the borders on the button will not be painted, and the button really won't be recognizable as a button.

We wrap up this fragment by adding the JButton object to the panel.
 
    myJButton.setToolTipText("This is a JButton");
    myJButton.setBorderPainted(false);
    this.add(myJButton);

We will wrap up this discussion with the code fragment that causes the button to appear when you touch it with the mouse pointer and causes it to disappear when you move the mouse pointer away.

This is a simple anonymous inner-class mouse listener that responds when the mouse enters and leaves the boundary of the button.  Except for the fact that it invokes the setBorderPainted() method with either true or false to expose the borders of the button or make them invisible, there is nothing new here.  You should be completely familiar with this approach to processing mouse events.

Note that in this program, we processed mouse events to cause the borders on the button to be either visible or invisible.

It is also very important to note that we weren't required to provide any special event handling to cause the icon on the button to change when the mouse pointer entered and exited the boundary of the button, and to change again when the button was pressed.  That behavior is automatic once you properly invoke the setRolloverIcon(), setPressedIcon(), and setRolloverEnabled() methods discussed earlier.

In a way, I hate to see this because it tends to break the structured nature of the API.  Normally it is necessary for us to provide event handlers to deal with these kinds of  events, but in this case, the event handling is being provided automatically.  If much of this sort of thing creeps into the API, it could lead to  confusion as to when it is and when it is not necessary to provide event handling.
 
    myJButton.addMouseListener(new MouseAdapter(){

      public void mouseEntered(MouseEvent mouseEvent){
        myJButton.setBorderPainted(true);
      }//end mouseEntered

      public void mouseExited(MouseEvent mouseEvent){
        myJButton.setBorderPainted(false);
      }//end mouseExited()

    });//end inner-class definition

.
 

Program Listing

This section contains a complete listing of the program. A listing of the source code for the utility program named PlafPanel02 is provided in the next section.
 
/*File Swing09 Copyright 1998, R.G.Baldwin
The primary purpose of this program is to demonstrate
"Hidden Buttons".  These are buttons which don't look
like buttons until you point to them with the mouse but
which "come alive" when you point to them.  By coming
alive, they take on the appearance of a button according
to the current look and feel.  This behavior is 
implemented using the new border capability of Swing.
Also, the image icon on the button changes to a different
image when you point to the button with the mouse. This
illustrates the new image icon capability of Swing.

The program illustrates the appearance of Hidden Buttons 
under different look and feel implementations.

The program also illustrates the use of tooltips.

And just for fun, the program also illustrates that every
Swing component is a container that can contain other
components.  This is done by building a pyramid of nested
JButton objects where each contains the objects above it.
It is interesting to observe the different appearance
of this pyramid under different L&F implementations.

This program requires that several icon sized gif files
be placed in the directory that contains the class
files. The names of the gif files must be bulb1, bulb2,
and bulb3.

These gif files are used to illustrate the different states
of image icons.

When the program starts, two Hidden Buttons and a pyramid
of buttons are displayed in a JFrame.  

In addition, a separate JFrame is displayed containing
buttons that can be used to select any of the different L&F
implementations currently installed on the system. When 
you select a L&F implementation on this JFrame, it is 
applied to all of the components on both JFrame objects.

One of the Hidden Buttons is a JButton.  The other is a
JToggleButton.  They are identified by the text on the
button as well as the tool tip that appears when you
pause with the mouse pointing at one of the buttons.

The buttons don't do anything when you click them because
no action listeners were registered on them.

Note that the JToggleButton doesn't respond to the 
rolloverIcon when it is in the pressed state.  It displays
the normalIcon instead.

Tested using JDK 1.1.6 and Swing 1.0.1 under Windows 95

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

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

// Subclass JFrame for the GUI
public class Swing09 extends JFrame {//controlling class

  public static void main(String args[]) {
    //Instantiate an object of this type
    Swing09 demoFrame = new Swing09();
    //Instantiate and link a PlafPanel to it
    PlafPanel02 plafPanel = new PlafPanel02(demoFrame);
  }//end main()
  //-----------------------------------------------------//
  
  Swing09() {//constructor
    this.setTitle("Copyright 1998, RG Baldwin");
    this.getContentPane().setLayout(new FlowLayout());
    this.getContentPane().add(new JLabel(
       "Hidden Button Demo, Copyright 1998, R.G.Baldwin"));
       
    //Instantiate and add the panel containing the hidden
    // buttons.
    this.getContentPane().add(new HiddenButtonPanel());
    
    //Just for fun, build a pyramid of nested JButton 
    // objects and observe their appearance under the 
    // different L&F implementations.
    JButton a = new JButton();
    JButton b = new JButton();
    JButton c = new JButton();
    JButton d = new JButton();
    JButton e = new JButton();
    JButton f = new JButton("Top");
    f.setToolTipText("Nested JButton objects");
    this.getContentPane().add(a);
    a.add(b);
    b.add(c);
    c.add(d);
    d.add(e);
    e.add(f);

    //Set size and display the GUI  
    this.setSize(400,280);
    this.setVisible(true);
    
    // Create an inner class WindowAdapter to terminate the
    // program when the JFrame is closed.
    this.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);}});//end WindowListener
    
  }//end constructor
  //-----------------------------------------------------//
   
}//end class Swing09
//=======================================================//

//This class is used to instantiate a panel containing
// two Hidden Buttons.
class HiddenButtonPanel extends JPanel{
  //Create new image icons using gif files.  You will need
  // to provide icon sized gif files with names that match
  // the file names in the following statements to be able
  // to run this program.  These images will appear on the
  // buttons in their different states. ImageIcon
  // implements the Icon interface so it can be used 
  // wherever an Icon is required (such as instantiation
  // of a JButton with an Icon).
  ImageIcon normalIcon = new ImageIcon("bulb3.gif");
  ImageIcon rolloverIcon = new ImageIcon("bulb2.gif");
  ImageIcon pressedIcon = new ImageIcon("bulb1.gif");

  //Declare references to two buttons of two different
  // types.
  JToggleButton myJToggleButton;
  JButton myJButton;
  
  HiddenButtonPanel(){//constructor
    //Create and prep the JButton object
    //Instantiate a JButton object with a caption and
    // an icon.
    myJButton = new JButton("JButton", normalIcon);
    //Establish icon to be displayed during mouse rollover
    myJButton.setRolloverIcon(rolloverIcon);
    //Establish icon to be displayed when button is pressed
    myJButton.setPressedIcon(pressedIcon);

    //Enable the Swing rollover effects.
    myJButton.setRolloverEnabled(true);
    //Set text for the tool tip for the button.
    myJButton.setToolTipText("This is a JButton");
    //Hide the border on the button
    myJButton.setBorderPainted(false);
    //Add the button to the panel
    this.add(myJButton);
    
    //Inner class listener to display and hide borders
    // on the button when the mouse enters and exits.
    myJButton.addMouseListener(new MouseAdapter(){
      public void mouseEntered(MouseEvent mouseEvent){
        //Display border on the button
        myJButton.setBorderPainted(true);
      }//end mouseEntered
      public void mouseExited(MouseEvent mouseEvent){
        //Hide the border on the button
        myJButton.setBorderPainted(false);
      }//end mouseExited()
    });//end inner-class definition

    //Create and prep the JToggleButton object  .  See
    // explanatory comments in the code for the JButton
    //above.
    myJToggleButton = new JToggleButton(
                             "JToggleButton", normalIcon);
    myJToggleButton.setRolloverIcon(rolloverIcon);
    myJToggleButton.setPressedIcon(pressedIcon);
    
    myJToggleButton.setRolloverEnabled(true);
    myJToggleButton.setToolTipText(
                               "This is a JToggleButton");
    myJToggleButton.setBorderPainted(false);
    this.add(myJToggleButton);
    
    //Inner class listener to respond to rollover
    myJToggleButton.addMouseListener(new MouseAdapter(){
      public void mouseEntered(MouseEvent mouseEvent){
        myJToggleButton.setBorderPainted(true);
      }//end mouseEntered
      public void mouseExited(MouseEvent mouseEvent){
        myJToggleButton.setBorderPainted(false);
      }//end mouseExited()
    });//end inner-class definition    
  }//end constructor  
}//end class HiddenButtonPanel
//=======================================================//
.
 

Program Listing for PlafPanel02

This section contains a complete listing for the utility program named PlafPanel02 that was used to implement the PL&F capability in this program.
 
 
/*File PlafPanel02 Copyright 1998, R.G.Baldwin
This is a revision to PlafPanel01.  This version is 
designed to stand alone inside its own JFrame object.
To use this program, simply instantiate an object of
this type and pass a reference to the JFrame under test
as a parameter.

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.

This class creates a JFrame. The JFrame 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.
  
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.
                           
Tested using JDK 1.1.6 and Swing 1.0.1 under Win95.
**********************************************************/

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

public class PlafPanel02 extends JFrame {
  /*
  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
  PlafPanel02 thisPlafPanel = this;//ref to this object
  
  public PlafPanel02(JFrame testGui) {//constructor
    this.testGui = testGui;//save ref to test GUI
    this.getContentPane().add(new JLabel(
       "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 JFrame.
      this.getContentPane().add((JButton)theButtons.
                                           elementAt(cnt));
    }//end for loop
    this.getContentPane().setLayout(new FlowLayout());
    this.setTitle("Copyright 1998, R.G.Baldwin");
    this.setBounds(30,300,350,150);
    this.setVisible(true);
  }//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 PlafPanel object
      SwingUtilities.updateComponentTreeUI(thisPlafPanel);
      //Set the L&F for the test GUI object
      SwingUtilities.updateComponentTreeUI(testGui);
                                    
    }//end actionPerformed()
  }//end class MyActionListener
  
  //main method for stand-alone testing
  public static void main(String[] args){
    new PlafPanel02(new JFrame());
  }//end main()
}//end class PlafPanel02
//=======================================================//
 .
-end-