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

Event Handling in JDK 1.1, Handling Events in Lightweight Components

Java Programming, Lecture Notes # 98, Revised 02/21/98.

Important Note to ACC Students

For a variety of reasons, including the fact that much of the material needed to understand lightweight components hasn't been covered in previous lessons, this lesson will not be included in Professor Baldwin's Intermediate Java Programming class at ACC.

In December of 1997, this lesson was expanded considerably and rewritten as lesson number 176 in the Advanced portion of the curriculum following lessons on components, layout managers, graphics, etc.

Preface

JDK 1.1 was formally released on February 18, 1997. This lesson was originally written on March 23, 1997 using the software and documentation in the JDK 1.1 download package.

Introduction

In a previous lesson on event handling, you learned how to create and service program-generated events under JDK 1.1.. That knowledge is critical to the handling of events in lightweight components, because lightweight components require the generation and servicing of events under program control.

Lightweight Components, A Discussion

The ability to create lightweight components is a new feature of JDK 1.1. This capability was added to deal with some problems under JDK 1.0

Specifically, creating new components in JDK 1.0 requires subclassing Canvas or Panel. This places each new component in its own opaque window based on the native components of the platform. Because native windows are opaque, they can't be used to implement transparent regions.

Also, native windows are handled differently across different platforms so the view of a user interface may differ from one platform to the next.

The ability to implement lightweight components in JDK 1.1 is referred to as the Lightweight UI Framework.

The Lightweight UI Framework

The Lightweight UI Framework makes it possible to extend the java.awt.Component class and the java.awt.Container class. This in turn makes it possible to create components that are not associated with native opaque windows.

These lightweight components mesh into the different AWT models allowing you to do layout, paint, handle events, etc., with no additional APIs required.

Lightweight components can have transparent areas. This is accomplished by simply not rendering those areas needing transparency in the overridden paint() method.

The lightweight component requires no native data-structures or peer classes. There is no native code required to process lightweight components. Thus, the handling of lightweight components is completely implemented in java code. This should lead to consistency across platforms.

Note the following except from the JDK 1.1 documentation:

You can mix lightweight components with the existing heavyweight components. For example, lightweight components can be made children of heavyweight containers or heavyweight components can be made children of lightweight containers

Heavyweight and lightweight components can be mixed within containers. Note however, that the heavyweight component will always be "on top" if it overlaps a lightweight component.

Additional useful information regarding the rationale, advantages, disadvantages, and cautions regarding lightweight components can be found in the JDK 1.1 documentation package.

With that as an introduction, let's move on to our sample program.

Sample Program

This program is designed to be compiled and run under JDK 1.1. The purpose of this program is to illustrate the creation and use of lightweight components.

There are two major aspects to the creation and use of lightweight components:

The first aspect has to do with the non-graphical characteristics of the component. In fact, the component could be a completely non-visual component (such as a timer component, for example). Here we are concerned with issues such as inheritance, event handling, etc. This gets us into the realm of the types of objects discussed in an earlier lesson on "Program-Generated Events."

The second aspect primarily has to do with the visual (graphic) aspects of the component. Since we haven't discussed graphics to any great degree up to this point in these lessons, we won't get into the graphic aspects of lightweight components in this lesson either. Rather, we will defer detailed discussion of the graphic aspects until a subsequent lesson where we discuss graphics in general.

This lesson will concentrate on the non-graphical aspects of lightweight components. Many of the concepts of this aspect of lightweight components were covered in the earlier lesson on "Program-Generated Events". It is critical that you understand that information before tackling this lesson.

This lesson is primarily an interpretation and discussion of a program that is based heavily on the "Round Button" example program in the JDK 1.1 documentation which is the intellectual property of Sun Microsystems.

In operation, this program creates and manipulates a lightweight visual component class named LWButton. A pseudocode overview of the LWButton class follows.


class LWButton extends Component {
  Declare instance variables:
    label //self-explanatory
    pressed //toggles true or false when LWButton is pressed
    actionListener //refers to a list of registered ActionListener objects

  LWButton();//constructs an LWButton with no label
  LWButton(String);//constructs an LWButton with a label
  
  //Add to or remove from a list of registered ActionListener objects
  public void addActionListener(ActionListener listener)
  public void removeActionListener(ActionListener listener)

  //Prepare to modify the appearance of the LWButton object and 
  // dispatch an ActionEvent object when the LWButton is pressed by 
  // the user. Invoke repaint() method.
  public void processMouseEvent(MouseEvent e)
  
  //Set the label after LWButton is instantiated
  public void setLabel(String label) 
  
  //Overridden paint method deals with the appearance of the LWButton
  // based on whether it is currently "pressed" or "not pressed".
  public void paint(Graphics g)
  
  //This method is supposed to determine if a mouse event is inside the
  // LWButton component.  However, as of 3/21/97, there is a vertical 
  // offset error in the Win95 implementation of JDK 1.1 and this method
  // returns true if the mouse is outside but below the object.  It
  // returns false if the mouse is inside near the top of the object.
  // Otherwise, it seems to work OK.
  // NOTE: THE PROBLEM DESCRIBED ABOVE SEEMS TO HAVE BEEN RESOLVED IN
  // JDK 1.1.3.

  public boolean contains(int x, int y)
  
  //Provide preferred size and minimum size of LWButton
  public Dimension getPreferredSize()
  public Dimension getMinimumSize() 

}//end class LWButton
You will note that the class definition for LWButton incorporates the essential ingredients that were defined for program-generated events in an earlier lesson. An abbreviated version of the list of essential ingredients is repeated below for convenient reference. This program creates a Frame object containing two LWButton objects and one standard Button object. The background of the Frame object is yellow. The transparent nature of the LWButton objects is apparent because the yellow background shows through them. The background does not show through the opaque standard button.

Various outputs are produced on the screen as the buttons are manipulated using the mouse. Typical outputs might be as follows (with line breaks added manually to cause the material to fit the page).


Mouse entered for illustration only
Mouse exited for illustration only
Mouse entered for illustration only
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=LW Button A]
 on LWButton[,53,28,106x106]
Mouse exited for illustration only
Mouse entered for illustration only
Mouse exited for illustration only
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Std Button]
 on button0
Mouse entered for illustration only
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=LW Button B]
 on LWButton[,239,28,107x107]
Mouse exited for illustration only
When one of the LWButtons is pressed, the color of the button becomes darker to provide visual feedback to the user. Then when the mouse button is released, an ActionEvent object is dispatched to all ActionListener objects.

This program was tested using JDK 1.1.3 under Win95.

Interesting Code Fragments

The interesting code fragments in this program are those that were not covered in the previous lesson on program-generated events, and those that are not concerned with the graphical appearance of the lightweight components.

The first interesting code fragment instantiates two objects of the LWButton type. The label for one of the objects is passed as a string to the constructor. The label for the other object is set after the object is instantiated.


    LWButton aLWButton = new LWButton("LW Button A");
    Button myStdButton = new Button("Std Button");    
    LWButton bLWButton = new LWButton("");

    bLWButton.setLabel("LW Button B");
After the LWButton objects are instantiated, ActionListener objects are instantiated and registered on them by invoking the addActionListener() method that is defined in the LWButton class.


    aLWButton.addActionListener(new MyActionListener());
    bLWButton.addActionListener(new MyActionListener());
    myStdButton.addActionListener(new MyActionListener());
Then the new LWButton objects are added to a Frame object using standard code.


    myFrame.add(aLWButton);
    myFrame.add(myStdButton);
    myFrame.add(bLWButton);
Some standard ActionListener classes are defined to provide ActionListener objects to be registered on the LWButton objects.

Then we enter the code for the lightweight component class definition. The first interesting code fragment in the class is the declaration of instance variables.


  String label; // The label on the button
  boolean pressed = false; // Becomes true if the button is pressed.
  //Refers to a list of ActionListener objects
  ActionListener actionListener;
The instance variables named label and actionListener have the same meaning as the similar instance variables in the sample program in an earlier lesson on "Program-Generated Events."

The instance variable named pressed is used in the logic for the LWButton object to decide when to cause it to change color and also to decide when to generate an ActionEvent.

This is followed by a pair of (almost) standard constructors. There is one aspect of the constructors that we have not seen before. In particular, the following statement is contained in the constructor which invokes the enableEvents() method.


     enableEvents(AWTEvent.MOUSE_EVENT_MASK);
Here is the description of the enableEvents() method taken directly from java.awt.Component class documentation. Now let's look at enableEvents() from a different viewpoint. The following comes from the description of the processMouseEvent() method of the Component class. The reason that this is important is because we will be overriding the processMouseEvent() method later. So, the reason for the statement in the constructor to enable mouse events is to make it possible to override the processMouseEvent() method, and to have it automatically invoked by the runtime system whenever a mouse event occurs.

This is an important milestone. Now we have learned two different ways to trap and process events under JDK 1.1.

Both approaches are used in this program.

The next interesting code fragment is the following method that creates a list of registered ActionListener objects which are to be notified whenever the LWButton object generates an ActionEvent. This is essentially the same code that we saw in an earlier lesson that discussed program-generated events. The use of the AWTEventMulticaster.add() method to create the list was discussed in detail in that lesson and shouldn't need to be repeated here.


  public void addActionListener(ActionListener listener) {     actionListener = AWTEventMulticaster.add(actionListener, listener);   }//end addActionListener()
This code is followed by similar code to remove an ActionListener object from the list which we won't discuss here.

This brings us to a section of code that is different from any that we have seen before, although it does contain elements of action event dispatching that we saw in the lesson on program-generated events. In this class, we override the processMouseEvent() method of the Component class. The purpose is twofold. When the user clicks on one of our new LWButton objects, we want to

In the final analysis, this method does not actually modify the appearance of the LWButton object. Rather, it sets up some parameters and then invokes repaint(). This causes the overridden paint() method to be invoked which actually modifies the appearance of the object. (As mentioned in an earlier lesson, there are other circumstances, such as moving a window, that can cause the paint() method to be invoked.)

Remember that I said earlier that because we have enabled mouse events, the processMouseEvent() method will be called automatically any time a mouse event occurs on the component. Therefore, one of our tasks will be to distinguish among the different types of mouse events.

Our code will use a fairly typical switch statement inside the processMouseEvent() method to distinguish among the various types of mouse events. The beginning portion of the switch statement is shown in the next code fragment.


    switch(e.getID()) {       case MouseEvent.MOUSE_PRESSED:         pressed = true;         repaint();          break;
This code fragment detects a MOUSE_PRESSED event and sets the pressed instance variable of the object to true. Then the repaint() method is called. This causes the paint() method to be called. The logic in the overridden paint() method uses the value of the pressed instance variable to decide what to do about the color of the LWButton object.

The next code fragment detects a MOUSE_RELEASED event and does a couple of things.


      case MouseEvent.MOUSE_RELEASED:
        if(actionListener != null) {//if an ActionListener is registered
           actionListener.actionPerformed(new ActionEvent(
               this, ActionEvent.ACTION_PERFORMED, label));
        }//end if on actionListener
        if(pressed == true) {
          pressed = false;
          repaint();
        }//end if on pressed
        break;
First, it confirms that the list of registered ActionListener objects is not empty, and if the list is not empty, it invokes the actionPerformed() method on the reference to the list. This is the same as the code that we saw in an earlier lesson on program-generated events.

Then it executes some logic involving the pressed instance variable and calls repaint() to cause the overridden version of paint() to be invoked to modify the appearance of the LWButton object.

Following this, we have some code to detect the MOUSE_ENTERED and MOUSE_EXITED events and to display an announcement on the screen of the occurrence of these events.


     case MouseEvent.MOUSE_ENTERED:
        System.out.println("Mouse entered for illustration only");
        break;
      case MouseEvent.MOUSE_EXITED:
        System.out.println("Mouse exited for illustration only");
        break;
A recommendation in the JDK 1.1 documentation (which we saw in an excerpt earlier) is that you should always finish your overridden processMouseEvent() method with the following statement to make certain that all possible mouse events are properly handled.


   super.processMouseEvent(e);
Next we find two methods that deal with the preferred size and minimum size of the LWButton objects. This information is used by various layout managers when constructing a visual layout. These two methods involve some graphics methods that we have not studied yet, so we will defer a discussion of this material to a subsequent lesson involving those graphics methods.

We also have a couple of methods that are used to set and get the value of the label instance variable. These methods are so simple that they don't merit further discussion.

That brings us to the overridden paint() method. As mentioned earlier, this method uses the current value of the pressed variable to manipulate the color of the LWButton object. When the button is not pressed, it is transparent. When it is pressed, it becomes non-transparent and much darker in color. Virtually all of the code in the overridden paint() method uses graphics methods that we have not studied yet. Again, we will defer a detailed discussion of those methods to a subsequent lesson involving graphics.

And finally, this brings us to a method named contains() that is supposed to use graphics methods to determine if the mouse click is inside an LWButton object when a mouse event occurs.

However, as of 3/23/97, there is an apparent bug in the JDK 1.1 software (at least in the Win95 implementation) that causes the y-coordinate of the mouse pointer to be reported with an erroneous offset. Therefore, this method returns false if the pointer is inside but near the top of the LWButton object, and returns true if the mouse pointer is outside but near the bottom of the LWButton object. Otherwise, it seems to work properly. (This problem seems to have been resolved in JDK 1.1.3.

Program Listing

This section contains a complete listing of the program with numerous additional comments. Refer to previous sections for an operational description of the program.


/* File LWButton.java Copyright 1997, R.G.Baldwin
This program is designed to be compiled and run under JDK 1.1.

The purpose of this program is to illustrate the creation and use of
lightweight components.
This program creates a Frame object containing two LWButton objects and
one standard Button object.  The background of the Frame object is yellow.
The transparent nature of the LWButton objects is apparent because the
yellow background shows through them.  The background does not show 
through the opaque standard button.

Various outputs are produced on the screen as the buttons are manipulated
using the mouse.

A typical output might be as follows:

Mouse entered for illustration only
Mouse exited for illustration only
Mouse entered for illustration only
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=LW Button A] on LWButton[,53,28,106x106]
Mouse exited for illustration only
Mouse entered for illustration only
Mouse exited for illustration only
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Std Button] on button0
Mouse entered for illustration only
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=LW Button B] on LWButton[,239,28,107x107]
Mouse exited for illustration only

When one of the LWButtons is pressed, the color of the button becomes
darker to provide visual feedback to the user.  When the LWButton is 
pressed and then the mouse button is released, an ActionEvent object
is dispatched to all ActionListener objects.

This program was tested using JDK 1.1 under Win95.
*/
//=======================================================================
import java.awt.*;
import java.awt.event.*;
//==========================================================================
public class Lightweight02 {
  public static void main(String[] args){
    GUI gui = new GUI();//instantiate a Graphical User Interface object
  }//end main
}//end class Container03
//=========================================================================
class GUI{
  public GUI(){//constructor
    Frame myFrame = new Frame("Copyright 1997, R.G.Baldwin");
    myFrame.setLayout(new FlowLayout());
    myFrame.setBackground(Color.yellow);
    
    LWButton aLWButton = new LWButton("LW Button A");
    Button myStdButton = new Button("Std Button");    
    LWButton bLWButton = new LWButton("");

    bLWButton.setLabel("LW Button B");    

    aLWButton.addActionListener(new MyActionListener());
    bLWButton.addActionListener(new MyActionListener());
    myStdButton.addActionListener(new MyActionListener());
    
    myFrame.add(aLWButton);
    myFrame.add(myStdButton);
    myFrame.add(bLWButton);
    
    myFrame.setSize(400,200);
    myFrame.setVisible(true);

    myFrame.addWindowListener(new Terminate());
  }//end constructor
}//end class GUI
//========================================================================
//Class to respond to action events
class MyActionListener implements ActionListener{
  public void actionPerformed(ActionEvent e){
    System.out.println(e);
  }//end actionPerformed
}//end class MyActionListener
//========================================================================
class Terminate extends WindowAdapter{
  public void windowClosing(WindowEvent e){
    System.exit(0);//terminate the program when the window is closed  
  }//end windowClosing
}//end class Terminate

//########################################################################
//  The following class produces the LW Button Component 
//========================================================================
class LWButton extends Component {
  String label; // The label on the button
  boolean pressed = false; // Becomes true if the button is pressed.
  ActionListener actionListener;//Refers to a list of ActionListener objects
  
  //-----------------------------------------------------------------------
  public LWButton() {//Constructs a LWButton with no label.
      this("");//invoke the next constructor with an empty string
  }//end constructor

  public LWButton(String label) {//Constructs a LWButton with label.
      this.label = label;
      enableEvents(AWTEvent.MOUSE_EVENT_MASK);
  }//end constructor
  //-----------------------------------------------------------------------
  //The following method constructs a list of ActionListener objects that
  // are registered on a particular LWButton object
  public void addActionListener(ActionListener listener) {
    actionListener = AWTEventMulticaster.add(actionListener, listener);
  }//end addActionListener()
  //-----------------------------------------------------------------------
  //The following method removes ActionListener objects from the list 
  // described above
  public void removeActionListener(ActionListener listener) {
     actionListener = AWTEventMulticaster.remove(actionListener, listener);
  }//end removeActionListener

  //---------------------------------------------------------------------
  //The  purpose of this method is twofold:
  // 1.  Modify the appearance of the LWButton object.
  // 2.  Invoke the actionPerformed() method in the Listener object that
  //     is registered to listen to this LWButton object.
  //This method is automatically called whenever a mouse event occurs on the
  // component and the method enableEvents(AWTEvent.MOUSE_EVENT_MASK) 
  // has been previously invoked on the component.
  public void processMouseEvent(MouseEvent e) {
    Graphics g;
    switch(e.getID()) {
      case MouseEvent.MOUSE_PRESSED:
        //When the mouse is pressed on the LWButton object, set the "pressed"
        // state of the object to true and force it to be repainted to change
        // its appearance.
        pressed = true;
        repaint(); 
        break;
      case MouseEvent.MOUSE_RELEASED:
        //When the mouse is released on the LWButton object, do two things:
        // 1. Invoke the actionPerformed() method in the Listener objects that
        //    are registered to listen to this LWButton object.
        // 2. Confirm that the "pressed" state is true and if so, set it to
        //    false and force the object to be repainted to change its
        //    appearance.
        if(actionListener != null) {//if an ActionListener is registered
           actionListener.actionPerformed(new ActionEvent(
               this, ActionEvent.ACTION_PERFORMED, label));
        }//end if on actionListener
        if(pressed == true) {
          pressed = false;
          repaint();
        }//end if on pressed
        break;
      case MouseEvent.MOUSE_ENTERED:
        System.out.println("Mouse entered for illustration only");
        break;
      case MouseEvent.MOUSE_EXITED:
        System.out.println("Mouse exited for illustration only");
        break;
    }//end switch
    super.processMouseEvent(e);
  }//end paint
  
  //---------------------------------------------------------------------
  //The following two methods provide the preferred size and the 
  // minimum size to be used by the various layout managers.  
  public Dimension getPreferredSize() {//overridden getPreferredSize()
    Font f = getFont();
    if(f != null) {
      FontMetrics fm = getFontMetrics(getFont());
      int max = Math.max(fm.stringWidth(label) + 40, fm.getHeight() + 40);
      return new Dimension(max, max);
    } else return new Dimension(100, 100);
  }//end getPreferredSize()
  
  public Dimension getMinimumSize() {//overridden getMinimumSize()
      return new Dimension(100, 100);
  }//end getMinimumSize()
  //------------------------------------------------------------------------
  //The following two methods are available to modify the label of
  // an LWButton object.
  public String getLabel() {//gets the label
    return label;
  }//end getLabel()
  
  public void setLabel(String label) {//sets the label
    this.label = label;
    invalidate();
    repaint();
  }//end setLabel()
  //---------------------------------------------------------------------    
  //The following overridden paint() method paints the LWButton, with the 
  // appearance of the button being determined by whether the "pressed"
  // state of the button is true or false.
  public void paint(Graphics g) {//paints the LWButton
    int s = Math.min(getSize().width - 1, getSize().height - 1);
    
    // paint the interior of the button
    if(pressed) g.setColor(getBackground().darker().darker());
    else g.setColor(getBackground());
    g.fillArc(0, 0, s, s, 0, 360);
    
    // draw the perimeter of the button
    g.setColor(getBackground().darker().darker().darker());
    g.drawArc(0, 0, s, s, 0, 360);
    
    // draw the label centered in the button
    Font f = getFont();
    if(f != null) {
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
                   s/2 - fm.stringWidth(label)/2,
                   s/2 + fm.getMaxDescent());
    }//end if on f!=null
  }//end overridden paint() method
  //--------------------------------------------------------------------------
  //This method is supposed to determine if a mouse event is inside the
  // LWButton component.  However, as of 3/21/97, there is a vertical 
  // offset error in the Win95 implementation of JDK 1.1 and this method
  // returns true if the mouse is outside but below the object.  It
  // returns false if the mouse is inside near the top of the object.
  // Otherwise, it seems to work OK.
  // NOTE: THE PROBLEM DESCRIBED ABOVE SEEMS TO HAVE BEEN RESOLVED IN
  // JDK 1.1.3.
  public boolean contains(int x, int y) {
     int mx = getSize().width/2;
     int my = getSize().height/2;
     return (((mx-x)*(mx-x) + (my-y)*(my-y)) <= mx*mx);
  }//end contains()
  //---------------------------------------------------------------------------  
}//end class LWButton
//===========================================================

Review

Review material for this lesson is not being included at this time because the lesson is not being included in the Intermediate Java Programming course at ACC at this time.

-end-