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

JDK 1.1, Lightweight Components, A Lightweight 3D Button Class

Java Programming, Lecture Notes # 176, Revised 02/24/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.

JDK 1.1 was formally released on February 18, 1997. This lesson was originally written as lesson number 98 on March 23, 1997 using the software and documentation in the JDK 1.1 download package. However, it was later decided that the placement of lightweight component material at that location in the sequence of lessons was premature and that it should be deferred until a variety of other topics had been covered. In December of 1997, the original material was expanded considerably, rewritten as lesson number 176, and placed in the lesson sequence following graphics, components, layout managers, etc.

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. If you don't remember that material, you should go back and review it.

Following a general discussion on lightweight components, this lesson develops and discusses a class for a lightweight version of a 3D button that generally mimics the behavior of a standard heavyweight button. No code is provided in this lesson to exercise the lightweight button class. Rather, a subsequent lesson provides several different programs that can be used to exercise the lightweight button under different layout managers.

Lightweight Components, A Discussion

The ability to create lightweight components was added as 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 required subclassing Canvas or Panel. This placed 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 User Interface (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 or other native widgets.

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. Also, it appears that in many cases, lightweight components are more responsive to mouse actions than their native counterparts.

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:
 
"We are using this framework in an upcoming version of the toolkit (beyond 1.1) to implement pure-java versions of the base UI controls (Button, List, etc.) which implement a common look-and-feel across the platforms (and don't use the native peers)."
In this quotation, JavaSoft is apparently referring to the Swing component of their Java Foundation Classes. The Swing component is available in pre-beta form for downloading and evaluation as of December of 1997. If you haven't evaluated Swing yet, you should do so. You will find that it contains a large number of extremely useful widgets, all implemented as lightweight components.

In addition to the use of lightweight components that will be provided by JavaSoft (and doubtless many other companies and individuals as well), you can also design and create your own lightweight components.

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 own lightweight button class.

The Lightweight Button Class

This lightweight button class is designed to be compiled and run under JDK 1.1 or later version. The purpose of the class is to illustrate the creation 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 (graphics) aspects of the component. In this lesson, we will develop a lightweight 3D button that behaves much as you would expect a heavyweight button to behave in many applications. Before using this lightweight button design, however, you should stop and ask yourself if any additional features need to be added to make it compatible with a user's normal expectations of a button (for example, it is not designed to respond to MOUSE_ENTERED and MOUSE_EXITED events).

In this lesson, we will proceed somewhat differently from our approach in many of the previous lessons. In particular, we will develop and discuss a class which implements our lightweight button. Then we will develop several other applications which exercise the lightweight button using different layout managers. However, we will defer the development of those applications to subsequent lessons. Thus, this lesson will concentrate solely on the lightweight button class.

In an earlier lesson, we listed three essential ingredients of a class that you may use to create your own components. That lesson dealt only with non-visual components. Since this lesson deals with a visual component, we will add a fourth essential ingredient having to do with rendering the component on the screen. An abbreviated version of the list of essential ingredients (with the fourth ingredient added) is repeated below for convenient reference.

This class is used to instantiate a 3D lightweight button object that behaves much like a heavyweight Button object but is much more responsive than a heavyweight button under JDK 1.1.3 and Win95 on a 133 mhz Pentium processor.

The color of the lightweight button is based on the background color of its container, but is one shade brighter than the color of the background when it appears to protrude out of the screen.

Normally, the lightweight button appears to protrude slightly out of the screen with highlights on the left and top edges and shadows on the bottom and right edges. Note that the highlighting only works if the background color does not contain RGB components with values at or near 255. This is because the brighter() method is invoked on the background color to provide the highlights. If the background color contains RGB components at the high end of their limit, the brighter() method has no effect (the colors cannot be made any brighter).

When you click the lightweight button with the mouse, it appears to retreat into the screen and then pops back out. As with a heavyweight button, this causes it to gain the focus.

When the lightweight button appears to retreat into the screen, its color changes to match that of the background with heavy shadows on the left and top and a faint outline on the bottom and right.

Whenever the lightweight button has the focus, its text is rendered in bold italics. This simulates the halo that appears around the text on a a heavyweight button when it has the focus under Win95. When it doesn't have focus, the text label is rendered as plain text.

When you click the lightweight button with the mouse, it generates an Action event. Also, if you press the space bar when the lightweight button has the focus, it generates an Action event.

This class was tested using JDK 1.1.3 under Win95.

Interesting Code Fragments

The first interesting code fragment is the first line in the class definition which is provided here as a reminder that one way to create a lightweight component is to extend the Component class.
 
class LWButton01 extends Component {
Early in the class definition we declare several instance variables that are used throughout the class. For example, we maintain two different versions of the label for the button: rawLabel and label. The first is the label specified by the user to be displayed on the lightweight button. The second is the same text string with two space characters concatenated onto each end which is what is actually rendered onto the lightweight button. The space characters are used to separate the ends of the text label from the ends of the button.
 
  String rawLabel;
  String label;
  boolean pressed = false; 
  boolean gotFocus = false;
  ActionListener actionListener;
An instance variable named pressed is used by the overridden paint() method to determine if the lightweight button has been pressed or has been released.

Similarly, an instance variable named gotFocus is true whenever the lightweight button has the focus and is used to cause the text on the button to be rendered in bold italics as a visual indication of focus.

The instance variable named actionListener is a reference to a list of registered listener objects. This was the first of the essential ingredients provided in the earlier list of essential ingredients. The use of this instance variable will be explained in more detail later.

Moving along to the next code fragment, as is the case with the heavyweight button class, you can instantiate a lightweight button of this class either

Thus, there are two overloaded versions of the constructor. The first, which takes no parameters, simply invokes the second passing an empty string as a parameter.

As mentioned earlier, two different versions of the String label are maintained. One is the raw label passed in as a parameter. The other is the same text but with two spaces appended on each end. This second version is actually rendered onto the lightweight button, providing space between the ends of the label and the ends of the button.

The two overloaded versions of the constructor are shown below.
 
  //Constructor for an LWButton with no label.
  public LWButton01() {
    //Invoke the parameterized constructor with an 
    // empty string
    this("");
  }//end constructor

  //Constructor for an LWButton with a label.
  public LWButton01(String rawLabel) {
    this.rawLabel = rawLabel;
    //Add spaces on either end and save it that way
    this.label = "  " + rawLabel + "  ";
    
    enableEvents(AWTEvent.MOUSE_EVENT_MASK |
                 AWTEvent.FOCUS_EVENT_MASK |
                 AWTEvent.KEY_EVENT_MASK);
  }//end constructor
  //-----------------------------------------------------//
A very significant aspect of the second overloaded version of the constructor is the invocation of the method named enableEvents().

You may remember that there are two different ways to service events in the JDK 1.1 event model. The most common way is based on event sources and event listeners, and we have provided many examples that operate according to that approach.

The second approach does not use listener objects, but rather uses a pair of methods consisting of enableEvents() and process...Event() where the ... indicates the name of an event type.

The operation using these two methods is relatively simple. If the method named enableEvents() is invoked in the constructor for a class with a specific event type being specified as a parameter, then whenever an event of that type occurs on an object of that class, a corresponding method named process...Event() will be invoked where the ... matches the type of event specified in the call to enableEvents().

Event types are specified in the parameter list of enableEvents() by passing one or more OR'ed symbolic constants (which are defined in the AWTEvent class) as parameters. The invocation of enableEvents() in the constructor for this class (shown above) causes the methods processMouseEvent(), processFocusEvent(), or processKeyEvent() to be invoked whenever an event of one of those types occurs on an object of this class. These three methods are overridden in this class to provide the desired behavior for each type of event.

Now let's move on to the next interesting code fragment.

The second item in the list of essential ingredients provided earlier was a method for creating and maintaining a list of registered listener objects. In this case, our lightweight button will multicast Action events, and therefore we must create and maintain a list of listener objects registered to be notified of such events.

We could create and maintain this list using some of the data structures classes such as the Vector class. However, there is a standard class named AWTEventMulticaster that is provided to help us in this regard. The AWTEventMulticaster class not only assists us by creating and maintaining the list, it is also used to notify all registered listeners whenever an event occurs. Therefore, it is a very useful class that can save us a lot of programming effort.

The Java Beans design patterns for the registration of listener objects are a pair of methods named add...Listener() and remove...Listener() where the ... indicates the type of events being registered. Therefore, our class needs a pair of methods having these names which we provide as addActionListener() and removeActionListener().
 
  public void addActionListener(ActionListener listener) {
    actionListener = AWTEventMulticaster.add(
                                 actionListener, listener);
  }//end addActionListener()
  //-----------------------------------------------------//
  
  public void removeActionListener(ActionListener listener){
     actionListener = AWTEventMulticaster.remove(
                                 actionListener, listener);
  }//end removeActionListener
We use the AWTEventMulticaster class to create and maintain our list by invoking the static add and remove methods of that class as shown above.

Note that in both cases, our method receives an object of type ActionListener (an object of a class that implements the ActionListener interface). We simply pass that object along to the add or remove method of the AWTEventMulticaster class. These methods return a reference to the list which we then assign to our instance variable named actionListener. Later we will use this reference variable (with help from the AWTEventMulticaster class) to notify all of the registered listener objects that an Action event has occurred on the lightweight button.

Now let's turn our attention to the next code fragment.

The standard operation for a heavyweight button is to respond to the space bar as though it were the left mouse button when the heavyweight button has the focus. The next code fragment is intended to simulate that behavior on the lightweight button. While we do simulate that behavior, we do not duplicate it exactly. The heavyweight button appears to be immune to the fact that holding the space bar down generates a repeated string of space characters. Our lightweight button is not immune. Holding the space bar down generates a series of events. We would have to add more logic (or interpret the key event differently) to cause it to ignore the additional space characters and duplicate the behavior of the heavyweight button.

This will be the first in a series of three methods of the process...Event() variety. This particular method is designed to process key events. Recall from the above discussion that this method is automatically invoked whenever a key event occurs on a lightweight button while it has the focus. If this isn't clear, you should go back to an earlier page and review it again, or perhaps go back and review an earlier lesson on the subject.

An extremely important rule when invoking any of the process...Event() methods is that you must execute the statement super.process...Event() at the end of your method. The possibility of forgetting to do this, with the attendant possibility of unpredictable results at runtime, is probably one of the reasons that the JavaSoft documentation advises you to exercise caution when using this approach to event handling.
 
For an explanation of this rule and a sample program, see the following reference in the JavaSoft JDK 1.1.3 documentation (your path may begin in a different directory): 

file:///C|/java_jdk/java/docs/guide/awt/designspec/events.html

Also see lesson 102 which contains an excerpt from that JavaSoft document as well as a sample program from JavaSoft showing an unconditional call to super.processFocusEvent(e) at the end of an overridden method named processFocusEvent(FocusEvent e). Be aware that not all books show this as an unconditional call.

The logic in this method is pretty straightforward. When the method is invoked, it receives an object of type KeyEvent. It examines this object to determine if the keyboard character is the space character, and if not, returns without doing anything other than invoking the same method on its superclass.

If the character is a space, then the code distinguishes between KEY_PRESSED and KEY_RELEASED.

On KEY_PRESSED, a call is made to the processMouseEvent() method, passing MOUSE_PRESSED as a parameter.

On KEY_RELEASED, a call is made to the processMouseEvent() method passing MOUSE_RELEASED as a parameter.

In effect, the method converts these two key events to a pair of corresponding mouse events and dispatches them as mouse events.
 
  public void processKeyEvent(KeyEvent e) {      
    if((e.getID() == KeyEvent.KEY_PRESSED) 
                               && (e.getKeyChar() == ' ')) 
      processMouseEvent(new MouseEvent(
           this,MouseEvent.MOUSE_PRESSED,0,0,0,0,0,false));
                
    if((e.getID() == KeyEvent.KEY_RELEASED) 
                                && (e.getKeyChar() == ' '))
      processMouseEvent(new MouseEvent(
          this,MouseEvent.MOUSE_RELEASED,0,0,0,0,0,false));
    
    super.processKeyEvent(e);    
  }//end processKeyEvent()  
Now let's consider the next code fragment which consists of the second in a series of methods of the process...Event() variety.

Later we will see that when a MOUSE_RELEASED event occurs on our lightweight button, the lightweight button requests the focus. Assuming that the request is successful, this will cause a FOCUS_GAINED event to occur on the lightweight button.

Similarly, when the lightweight button has the focus and it moves to another component for any reason, a FOCUS_LOST event will occur on the lightweight button.

Knowing whether or not the lightweight button has the focus is important in two respects. First, the button will respond to key events when it has the focus.

Second, in order to provide visual feedback regarding focus to the user, the manner in which the lightweight button is rendered should be different when it has, and when it does not have the focus.

The overridden paint() method for this lightweight button renders the text label on the button in bold italics when it has the focus, and renders it plain otherwise. Thus a lightweight button without a label wouldn't have any indication of focus. This is the type of thing that I was referring to earlier when I cautioned against using this lightweight button class for serious programming without considering the need for additional or different functionality.

The code in the following method traps focus events and sets an instance variable named gotFocus to true when the lightweight button gains the focus and sets it to false when the button loses the focus. This instance variable is used by code elsewhere in the program to render the text on the lightweight button accordingly.

After setting the gotFocus flag to true or false, this method causes the lightweight button to be repainted so that the change in the style of the text will become immediately apparent to the user. According to the documentation for JDK 1.1.3, the call to the invalidate() method marks the lightweight button and all its parents as needing to be laid out before repainting the screen.

Again, this method is invoked automatically whenever a focus event occurs on the lightweight button.
 
  public void processFocusEvent(FocusEvent e) {
    if(e.getID() == FocusEvent.FOCUS_GAINED) 
      gotFocus = true; //set the gotFocus flag
    if(e.getID() == FocusEvent.FOCUS_LOST) 
      gotFocus = false; //clear the gotFocus flag
    this.invalidate();      
    this.repaint();
    
    super.processFocusEvent(e);    
  }//end processFocusEvent()
The next code fragment is the third in the series of process...Event() methods that are automatically invoked whenever an event of the matching type occurs on a lightweight button. This method has three primary purposes:

The second item brings us to another topic that we have discussed in earlier lessons, but which we will review here for convenience. As mentioned above, the add() and remove() methods of the AWTEventMulticaster class can be used to create and maintain the list of registered listener objects.

The AWTEventMulticaster class provides another very important service -- listener notification.

Recall that the add() and remove() methods of AWTEventMulticaster return a reference to an object of type ActionListener (which is really a reference to a list of ActionListener objects). We assigned that reference to an instance variable named actionListener.

This is the great news: all we have to do to invoke the actionPerformed() method on every listener object in the list is to invoke it once on the reference to the list (invoke the method on our instance variable named actionListener). The code in the AWTEventMulticaster class will then take responsibility for invoking the method on every object in the list.

Now back to the code fragment. Early in this discussion, you were introduced to an instance variable named pressed. All that this method really has to do to cause the appearance of the lightweight button object to be modified appropriately is to set this instance variable to either true or false.

The code in the overridden paint() method (that we discuss later) will test the state of the pressed instance variable and do the rest. When pressed is true, the button is rendered as though it has been pressed into the screen. Otherwise, it is rendered as though it protrudes out from the screen.

This method is automatically invoked whenever a mouse event occurs on the lightweight button (which includes an event created and dispatched by the key processor discussed earlier).

The method cracks open the incoming object to determine what kind of mouse event occurred. All event types other than MOUSE_PRESSED and MOUSE_RELEASED are ignored.

When the event type is MOUSE_PRESSED, the method sets the pressed instance variable and calls repaint() to cause the lightweight button to be rendered in its new image.

When the type is MOUSE_RELEASED, three things are done:

As you can see from the code below, when the actionPerformed() method is invoked, it is necessary to instantiate and pass an object of type ActionEvent which encapsulates the source object for the event (this), the type of event (ACTION_PERFORMED), and any string object that you want to pass to the method that will be processing the event (this string is referred to in the documentation as the command name).
 
  public void processMouseEvent(MouseEvent e) {
    switch(e.getID()) {//what kind of mouse event?
      case MouseEvent.MOUSE_PRESSED:
        pressed = true;
        this.invalidate();        
        this.repaint(); 
        break;
      case MouseEvent.MOUSE_RELEASED:
        //if an ActionListener is registered
        if(actionListener != null) {
          actionListener.actionPerformed(new ActionEvent(
               this, ActionEvent.ACTION_PERFORMED, label));
        }//end if on actionListener
        
        if(pressed == true) {
          pressed = false;
          this.requestFocus();
          this.invalidate();          
          this.repaint();
        }//end if on pressed
        break;
    }//end switch

    super.processMouseEvent(e);
  }//end processMouseEvent()
After we invoke the actionPerformed() method appropriately, we set the pressed flag to false, request the focus, and force a repaint.

We end the method with the mandatory call to the superclass version of the same method.

Getting on to the next code fragment, there are two properties of a component that are used by the various layout managers in laying out the components in a GUI: preferredSize and minimumSize.

These are read-only properties, so they don't have a set method. However, a component normally has a get method for each of these properties. The next code fragment provides the get methods for the preferredSize and minimumSize for our lightweight button.

In this case, the preferred size is based on the size required to accommodate the text in the label for the button. Here we use the version of the label that has two spaces appended on each end to provide space between the ends of the label and the ends of the button. We add ten pixels to the height of the label to establish the preferred height of the lightweight button.

If you don't understand the use of the FontMetrics class (as seen below) to obtain size information for the label, you can find a discussion of font metrics in an earlier lesson.
 
  public Dimension getPreferredSize() {
    if(getFont() != null) {
      FontMetrics fm = getFontMetrics(getFont());
      return new Dimension(fm.stringWidth(label), 
                                      fm.getHeight() + 10);
    } else return new Dimension(10, 10);//no font
  }//end getPreferredSize()
  
  //Override the getMinimumSize() method and specify
  // an arbitrary minimum size for the LWButton.
  public Dimension getMinimumSize() {
      return new Dimension(10, 10);
  }//end getMinimumSize()
Since I didn't have any rational basis for establishing a minimum size for the lightweight button, I arbitrarily set the minimum size to a square, ten pixels on each side. At this point, I'm not certain what conditions would cause this information to be used by a layout manager.

Regarding the next code fragment, some but not all of the standard heavyweight components have a property named label. When the property exists, it is normally a read/write property with both set and get methods included in the class definition. Our lightweight button has such a property and its state is stored in the two instance variables named rawLabel and label discussed earlier. Again, the difference is that two space characters are appended to each end of the rawLabel to produce the label. The value of label is actually used to render the lightweight button and the space characters provide space between the ends of the text and the ends of the button.

Whenever the getLabel() method is invoked on the lightweight button, the value of rawLabel is returned.

Whenever the setLabel() method is invoked on the button, the incoming value is stored in rawLabel, the modified version of the incoming value is stored in label, and repaint() is called to force the lightweight button to be rendered on the screen with the new label. The code is pretty straightforward.
 
  public String getLabel() {//gets the label
    return rawLabel;
  }//end getLabel()
  
  public void setLabel(String rawLabel) {
    this.rawLabel = rawLabel; //save the raw label
    //Add spaces to each end of the rawLabel to make it
    // easier to center the label in the LWButton.
    this.label = "  " + rawLabel + "  ";
    this.invalidate();
    this.repaint();
  }//end setLabel()
And that brings us to the overridden paint() method which is where all of the visual rendering takes place. The paint() method examines the values of instance variables established by code in other parts of the class and uses those values to make decisions about how to render the lightweight button.

In particular, the instance variable named pressed is used to decide whether the lightweight button should be rendered protruding from the screen, or pressed into the screen.

The instance variable named gotFocus is used to decide whether to render the text label plain or in bold italics to indicate that the lightweight button has the focus.

The 3D effect is achieved by rendering edges to be either darker or brighter to give the illusion of shadows or highlights. The actual mechanism for doing this by drawing offset rectangles was developed and explained in an earlier lesson that discussed various graphics techniques, and won't be discussed in any detail in this lesson. You are referred to the earlier lesson if you don't remember how those techniques are used.

Another trick that is used to give the illusion that the button has actually been pressed into the screen is to cause the text label to shift slightly down and to the right when the button is pressed (or up and to the left when the button pops back out). This trick along with a corresponding change in shadow position does a pretty good job of delivering the desired optical illusion.

In this version, the color of the lightweight button is tied to the color of the background of the container. When protruding from the screen, the color of the button is a brighter version of the background color suggesting that it receives more light because it protrudes. The left and top edges are highlighted with an even brighter version of the same color to suggest a light source that is up and to the left. The bottom and right edges are darkened using a darker version of the background color to give the illusion of shadows.

When the button is pushed into the screen, the color is changed to the color of the background to suggest that it no longer catches more light than the background. In addition, shadows are produced on the left and top edges to give the illusion that it has been physically pushed into the screen. A faint dark outline of the bottom and right edges is provided in this state so that the button can be distinguished from the background.

Despite the specification of preferred and minimum size for the lightweight button discussed earlier, the actual size on the screen is determined by the layout manager in use. The layout manager may or may not honor one, the other, or both of the dimensions of the preferred size. Therefore, it is necessary to determine the actual size before rendering the button and not rely on the preferredSize to tell us the size to render.

Now getting back to the code, this method is so large that it will be broken into separate code fragments. The first interesting fragment is used to cause the text label to be rendered differently depending on whether or not the lightweight button has the focus. If the gotFocus flag is true, the text is rendered in bold italics. Otherwise, it is rendered plain.

Although fairly complicated looking, this code is a straightforward application of font-manipulation techniques that we learned in previous lessons. Recall that all drawing is actually performed on the graphics context that is passed as a parameter to the paint() method. In this code, the name of the graphics context is g.
 
    if(gotFocus)
      g.setFont(new Font(getFont().getName(),
            Font.BOLD | Font.ITALIC,getFont().getSize()));
    else g.setFont(new Font(getFont().getName(),
                         Font.PLAIN,getFont().getSize()));
The remaining code in the overridden paint() method is divided into two categories based on whether the pressed flag is true or false.

We will begin by examining a series of statements that are executed when the pressed flag is true.

The first few statements that we see in the following code fragment manipulate the drawing color, fill a rectangle with color, and manipulate the placement of rectangles on the drawing surface to give the illusion of shadows. We manipulate the placement by specifying the coordinates of the upper left-hand corner of the rectangle in the first two parameters.

Recall that when we use rectangles in this way, two sides of the rectangle are actually off the drawing surface and the other two sides are on the drawing surface. Thus, each time we draw a rectangle, we are actually drawing a pair of orthoganal lines in the desired position. We could accomplish the same thing by drawing lines instead of rectangles, but more code would be required. We use rectangles simply for convenience because we get two lines for every statement.

The comments in this code pretty well describe what is going on.
 
    if(pressed){ //if the pressed flag is true
      g.setColor(getBackground());
      g.fillRect(//fill rectangle with background color
           0,0,this.getSize().width,this.getSize().height);
           
      //Draw shadows three shades darker than background
      g.setColor(
               getBackground().darker().darker().darker());
      //Note that three offset rectangles are drawn to
      // produce a shadow effect on the left and top of
      // the rectangle.               
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
      g.drawRect(
           1,1,this.getSize().width,this.getSize().height);
      g.drawRect(
           2,2,this.getSize().width,this.getSize().height);
           
      //Now draw a faint outline on the bottom and right of
      // the rectangle.
      g.setColor(getBackground().darker());
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
The next interesting code fragment in the pressed state uses FontMetrics to display the text label in the center of the lightweight button. Again, we have covered everything seen here in earlier lessons. You need to understand how this is being done, so if you have forgotten, you should go back and review the earlier lessons.
 
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
               getSize().width/2 - fm.stringWidth(label)/2,
                    getSize().height/2 + fm.getAscent()/2);
That concludes the code fragments for the case where the pressed flag is true and the lightweight button appears to have been pressed into the screen.

Now we will examine the case where the pressed flag is false and the lightweight button appears to be protruding from the screen.

Again, we begin by setting drawing colors, drawing filled rectangles, and drawing offset rectangles to give the illusion of highlights and shadows. As before, the comments pretty well explain what is going on.
 
    else{//not pressed
      //Make the protruding LWButton object one shade
      // brighter than the background.
      g.setColor(getBackground().brighter());
      g.fillRect(//and fill a rectangle
           0,0,this.getSize().width,this.getSize().height);

      //Set the color for the shadows three shades darker
      // than the background.
      g.setColor(
               getBackground().darker().darker().darker());
      //Draw two offset rectangles to create shadows on 
      // the right and bottom.               
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
      g.drawRect(
         -2,-2,this.getSize().width,this.getSize().height);
         
      //Highlight the left and top two shades brighter 
      // than the background, one shade brighter than the
      // color of the LWButton itself which is one shade
      // brighter than the background.
      g.setColor(
               getBackground().brighter().brighter());
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
And that brings us to the final step which is to display the text label on the protruding button at a position that is displaced up and to the left by two pixels relative to the position of the text when the button is pressed. This change in position as the button switches from depressed to protruding enhances the illusion that the button is actually moving.

The code shown below is the same as you saw earlier except for the offset of two pixels in the negative direction highlighted in boldface at the right-end of two lines below.
 
      //Now place the text in the LWButton object shifted
      // by two pixels up and to the left.           
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
           getSize().width/2 - fm.stringWidth(label)/2 - 2,
                getSize().height/2 + fm.getAscent()/2 - 2);
    }//end else
  }//end overridden paint() method
That concludes the discussion of interesting code fragments for the lightweight button class.

So what do we have at this point? We have a new class from which we can instantiate lightweight button objects and add them to containers for the construction of graphical user interfaces. To see how these lightweight buttons behave, we will need to devise some applications to exercise them.

We will defer demonstrations of the use of our lightweight buttons until we devise some test applications in lessons following this one.

Program Listing

A complete listing of the lightweight button class with extensive comments follows.
 
/* File LWButton01.java Copyright 1997, R.G.Baldwin

This class is used to instantiate a 3D lightweight button
object that behaves quite a bit like a heavyweight Button
object but is much more responsive than a heavyweight
button under JDK 1.1.3 and Win95 on a 133 mhz Pentium
processor.

The color of the button is based on the background color 
of its container, but is one shade brighter than the color
of the background. 

Normally, it appears to protrude slightly out of the 
screen with highlights on the left and top edges and 
shadows on the bottom and right edges.  Note that the
highlighting only works if the background color does not
contain components with values of 255.

When you click the button with the mouse, it appears to 
retreat into the screen and then pops back out.  As with
a heavyweight button, this causes it to gain the focus.

When it appears to retreat into the screen, its color 
changes to match that of the background with heavy shadows
on the left and top and a faint outline on the bottom and
right.

The visual indication of focus is that the text on the
button is rendered in bold italics.

When you click the button, it generates an action event.

When the button has the focus and you press the space
bar, it generates an action event.

This class was tested using JDK 1.1.3 under Win95.
*/
//=======================================================//
import java.awt.*;
import java.awt.event.*;
//=======================================================//
class LWButton01 extends Component {
  //Save the raw label provided by the user here to make
  // it available to the getLabel() method.
  String rawLabel;
  //The following instance variable contains the raw
  // label with two spaces appended to each end to make
  // it easier to use for sizing the LWButton.
  String label;
  // The following instance variable is set to true if 
  // the LWButton is pressed and not released.
  boolean pressed = false; 
  //The following instance variable is set to true when 
  // the LWButton has focus
  boolean gotFocus = false;
  //The following instance variable refers to a list of 
  // registered ActionListener objects.
  ActionListener actionListener;
  
  //-----------------------------------------------------//
  
  //Constructor for an LWButton with no label.
  public LWButton01() {
    //Invoke the parameterized constructor with an 
    // empty string
    this("");
  }//end constructor

  //Constructor for an LWButton with a label.
  public LWButton01(String rawLabel) {
    this.rawLabel = rawLabel;
    //Add spaces on either end and save it that way
    this.label = "  " + rawLabel + "  ";
    
    //Invoke the enableEvents() method so that the
    // processMouseEvent(), processFocusEvent(), and
    // processKeyEvent() methods will be automatically
    // invoked whenever an event of the corresponding
    // type occurs on the LWButton.  Note that this is
    // an alternative approach to the source/listener
    // event model.
    enableEvents(AWTEvent.MOUSE_EVENT_MASK |
                 AWTEvent.FOCUS_EVENT_MASK |
                 AWTEvent.KEY_EVENT_MASK);
  }//end constructor
  //-----------------------------------------------------//
  
  //The following method uses the AWTEventMulticaster 
  // class to construct a list of ActionListener objects 
  // that are registered on the 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

  /*-----------------------------------------------------//
  This method is used to cause the LWButton to behave as
  if a mouse event occurred on it whenever it has the
  focus and the space bar is pressed and then released.
  Holding the space bar down generates repetitive 
  events due to the repeat feature of the keyboard (this
  would need to be disabled in a real program).
  
  This method is automatically called whenever a key 
    event occurs on the LWButton and the method 
    enableEvents(AWTEvent.KEY_EVENT_MASK) has been 
    previously invoked on the LWButton.  */ 
     
  public void processKeyEvent(KeyEvent e) {
    //Generate mousePressed() event when the space bar 
    // is pressed by invoking the processMouseEvent()
    // method and passing an event object that 
    // impersonates a mouse pressed event.      
    if((e.getID() == KeyEvent.KEY_PRESSED) 
                               && (e.getKeyChar() == ' ')) 
      processMouseEvent(new MouseEvent(
           this,MouseEvent.MOUSE_PRESSED,0,0,0,0,0,false));

    //Generate mouseReleased() event when the space bar 
    // is released by invoking the processMouseEvent()
    // method and passing an event object that 
    // impersonates a mouse released event.                
    if((e.getID() == KeyEvent.KEY_RELEASED) 
                                && (e.getKeyChar() == ' '))
      processMouseEvent(new MouseEvent(
          this,MouseEvent.MOUSE_RELEASED,0,0,0,0,0,false));
    
    //The following statement is always needed when an
    // overridden version of processKeyEvent() is used.
    super.processKeyEvent(e);    
  }//end processKeyEvent()  

  /*-----------------------------------------------------//
  This method in invoked when a focus event occurs on the
    LWButton.  This happens when the requestFocus() method
    is called inside the mouseReleased() event handler for
    the LWButton.  This sets or clears the gotFocus flag 
    that is used to cause the text renderer to modify the
    text to indicate that the LWButton has the focus.
    When the LWButton has the focus, the text is rendered
    in bold italics.
  
  This method is automatically called whenever a focus 
    event occurs on the LWButton and the method 
    enableEvents(AWTEvent.FOCUS_EVENT_MASK) has been 
    previously invoked on the LWButton.  */  

  public void processFocusEvent(FocusEvent e) {
    if(e.getID() == FocusEvent.FOCUS_GAINED) 
      gotFocus = true; //set the gotFocus flag
    if(e.getID() == FocusEvent.FOCUS_LOST) 
      gotFocus = false; //clear the gotFocus flag
    this.invalidate();      
    this.repaint();
    
    //The following statement is always needed when an
    // overridden version of processFocusEvent() is used.
    super.processFocusEvent(e);    
  }//end processFocusEvent()

  /*-----------------------------------------------------//
  The  purpose of this method is twofold:
   1.  Modify the appearance of the LWButton object when
       the user clicks on it.
   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 LWButton and the method 
    enableEvents(AWTEvent.MOUSE_EVENT_MASK) has been 
    previously invoked on the LWButton.            */
    
  public void processMouseEvent(MouseEvent e) {
    switch(e.getID()) {//what kind of mouse event?
      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. When pressed is true, the button is
        // rendered as though it has been pressed into the
        // screen.
        pressed = true;
        this.invalidate();        
        this.repaint(); 
        break;
      case MouseEvent.MOUSE_RELEASED:
        //When the mouse is released on the LWButton object:
        // 1. Invoke the actionPerformed() method in the 
        //    Listener objects that are registered to 
        //    listen to the 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.
        //    When pressed is false, the button is rendered
        //    so as to appear to protrude out of the 
        //    screen.
        // 3. Request the focus for the LWButton object.
        
        //if an ActionListener is registered
        if(actionListener != null) {
          //Invoke the actionPerformed() method on the list
          // of listener objects registered on the 
          // LWButton object.  Instantiate and pass an
          // ActionEvent object as a parameter.
          actionListener.actionPerformed(new ActionEvent(
               this, ActionEvent.ACTION_PERFORMED, label));
        }//end if on actionListener
        
        if(pressed == true) {
          pressed = false;
          this.requestFocus();
          this.invalidate();          
          this.repaint();
        }//end if on pressed
        break;
    }//end switch

    //The following statement is always needed when an
    // overridden version of processMouseEvent() is used.
    super.processMouseEvent(e);
  }//end processMouseEvent()
  
  //-----------------------------------------------------//
  
  //The following two methods provide the preferred size 
  // and the minimum size of the LWButton to be used by 
  // the layout managers.  
  
  //Override the getPreferredSize() method.  Base the
  // preferred size on the size of the text in the
  // LWButton object.  Recall that two spaces have been
  // appended to each end of the text in the LWButton.
  public Dimension getPreferredSize() {
    if(getFont() != null) {
      FontMetrics fm = getFontMetrics(getFont());
      return new Dimension(fm.stringWidth(label), 
                                      fm.getHeight() + 10);
    } else return new Dimension(10, 10);//no font
  }//end getPreferredSize()
  
  //Override the getMinimumSize() method and specify
  // an arbitrary minimum size for the LWButton.
  public Dimension getMinimumSize() {
      return new Dimension(10, 10);
  }//end getMinimumSize()
  //-----------------------------------------------------//
  
  //The following two methods are available to get and set
  // the label of an LWButton object.
  public String getLabel() {//gets the label
    return rawLabel;
  }//end getLabel()
  
  public void setLabel(String rawLabel) {//sets the label
    this.rawLabel = rawLabel; //save the raw label
    //Add spaces to each end of the rawLabel to make it
    // easier to center the label in the LWButton.
    this.label = "  " + rawLabel + "  ";
    this.invalidate();
    this.repaint();
  }//end setLabel()
  //-----------------------------------------------------//
  
  //The following overridden paint() method paints the 
  // LWButton  The appearance of the LWButton depends on
  // the pressed and gotFocus flags.  When pressed is true,
  // the LWButton is rendered to appear that it has been
  // pressed into the screen.  When it is false, the
  // LWButton is rendered to appear that it is protruding
  // from the screen.  When gotFocus is true, the
  // text is rendered in bold italics as the visual 
  // indication that the LWButton has the focus.  When
  // gotFocus is false, the text is rendered plain.
  
  //Note also that the position of the text also depends
  // on the pressed flag.  When pressed is false, the text
  // is rendered slightly up and to the left of its
  // pressed position.  This enhances the illusion that
  // the button is being pressed into the screen.
  
  //The color of the LWButton object is tied to the
  // background color.  When protruding, the button is 
  // rendered one shade brighter than the background.  
  // When pressed, the LWButton is rendered the same color
  // as the background.

  //The actual size of the LWButton object is determined 
  // by the layout manager which may or may not honor the
  // preferred and minimum size specifications.
  
  public void paint(Graphics g) {//paints the LWButton
    //If LWButton has the focus, display the text in
    // bold italics.  Otherwise display plain.
    if(gotFocus)
      g.setFont(new Font(getFont().getName(),
            Font.BOLD | Font.ITALIC,getFont().getSize()));
    else g.setFont(new Font(getFont().getName(),
                         Font.PLAIN,getFont().getSize()));
    
    if(pressed){ //if the pressed flag is true
      g.setColor(getBackground());
      g.fillRect(//fill rectangle with background color
           0,0,this.getSize().width,this.getSize().height);
           
      //Draw shadows three shades darker than background
      g.setColor(
               getBackground().darker().darker().darker());
      //Note that three offset rectangles are drawn to
      // produce a shadow effect on the left and top of
      // the rectangle.               
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
      g.drawRect(
           1,1,this.getSize().width,this.getSize().height);
      g.drawRect(
           2,2,this.getSize().width,this.getSize().height);
           
      //Now draw a faint outline on the bottom and right of
      // the rectangle.
      g.setColor(getBackground().darker());
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
           
      //Now center the text in the LWButton object
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
               getSize().width/2 - fm.stringWidth(label)/2,
                    getSize().height/2 + fm.getAscent()/2);
    }//end if(pressed)
    
    else{//not pressed
      //Make the protruding LWButton object one shade
      // brighter than the background.
      g.setColor(getBackground().brighter());
      g.fillRect(//and fill a rectangle
           0,0,this.getSize().width,this.getSize().height);

      //Set the color for the shadows three shades darker
      // than the background.
      g.setColor(
               getBackground().darker().darker().darker());
      //Draw two offset rectangles to create shadows on 
      // the right and bottom.               
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
      g.drawRect(
         -2,-2,this.getSize().width,this.getSize().height);
         
      //Highlight the left and top two shades brighter 
      // than the background, one shade brighter than the
      // color of the LWButton itself which is one shade
      // brighter than the background.
      g.setColor(
               getBackground().brighter().brighter());
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
         
      //Now place the text in the LWButton object shifted
      // by two pixels up and to the left.           
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
           getSize().width/2 - fm.stringWidth(label)/2 - 2,
                getSize().height/2 + fm.getAscent()/2 - 2);
    }//end else
  }//end overridden paint() method
  //-----------------------------------------------------//
}//end class LWButton01
//=======================================================//
-end-