Section 6.3
Component Events


WHEN THE USER INTERACTS with a graphical interface component, the program should respond appropriately. The program is notified that the interaction has occurred by an event. Events are delivered to a program in the form of calls to certain methods. As a programmer, you have to specify how your program should respond to such events by defining appropriate methods.

A typical GUI program uses many user interface components, including buttons, check boxes, text input boxes, and so forth. The Java classes that implement these components were discussed in Section 2. As discussed in Section 1, components can be nested inside containers, which are themselves components that can be nested inside still other containers. With such a complex structure, it is important to understand exactly how events are handled.

Every component -- that is, every object belonging to some subclass of class Component -- has a method called handleEvent. Events are sent to the component by calling its handleEvent() method. This method might in turn handle some events by calling special purpose event-handling methods such as mouseMove() and action(). These methods were discussed in Section 5.2. For a program that uses multiple, nested components, two questions arise: When an event occurs, which component should it be sent to? And if an event is sent to a component that has not been programmed to handle such events, what happens to the event?


The second question is fairly easy to answer: If an event is sent to a component, and if that component does not handle the event, then the event will be sent to the container that contains the component or, if the component is not nested inside any container, then the event will be ignored. Thus, if you want to define a response to some event, you can program the response in the component that actually receives the event or in any component that contains that component.

Let's look at an example in more detail. Consider a Button, bttn, that is contained in a Panel, pnl, which is in turn contained in an Applet, aplt. When the button bttn is pressed, it generates an "action event," which is ordinarily handled in some component's action() method. The action event is first sent to bttn itself. If bttn does not handle the event -- the usual case -- it will be sent to the panel pnl. And if pnl also does not handle the event, it will be sent to the applet aplt. Finally, if aplt does not handle the event, then the system gives up and the event is simply ignored.

Now, by default, Buttons, Panels, and Applets all ignore action events. If you want the button to have any effect, you have to program either bttn, pnl, or aplt to respond to the action event that the button generates. You would do this by defining a subclass of Button, Panel, or Applet and overriding the action() method in that subclass to handle the event generated by the button. If you want the button to handle the event itself, you would override the Button class and define bttn to be an object of that subclass. Similarly, you could define pnl to be a member of a subclass of Panel, and you could program that subclass to handle action events. Finally, you could program the applet itself to handle the events generated by the button.

Of course, for aplt to be at all useful, it already has to be a member of a subclass of Applet, rather than of Applet itself. So it is tempting to let the applet handle all the action events generated by components in the applet. For simple applets, there is nothing wrong with this approach, but for more complex applets, it becomes unwieldy because there are too many components and too many possible events. You should think in terms of black boxes: If a panel has a certain function, and if a button is an internal part of the implementation of that function, then the button's action event should be handled by the panel. The applet shouldn't even have to know about it. This is a very common situation. (On the other hand, it would be rare to have a button with its own behavior, independent of the component that contains the button. So, it would be rare to override the Button class.)


The question of where events are sent in the first place turns out to be more difficult than it should be. (This is true partly because the documentation for Java 1.0 was not always clear about what should happen.) The problem in Java 1.0 is that certain components do not receive certain events. For examples, Labels do not receive any events at all. This is not too much of a problem, but it does make it impossible, for example, to write a subclass of Label that responds to mouse clicks. Even worse, TextComponents do not receive keyboard events in Java 1.0. This would make it impossible to program text components that react to certain characters in special ways.

Here is how things should work:

Although it's unfortunate that event-handling was not better defined in Java 1.0, it's usually not much of a problem unless you want to do something fancy, such as create a subclass of Label or TextField with special event-handling.

I should note that major changes to Java's event-handling model have been made in Java 1.1. However, the old event model, which is described in these notes, is still supported (at least for the time being). For relatively simple programs and applets, the original event model is probably superior. The new event model is more useful in large, complex projects.

I already discussed mouse events and keyboard events in Section 5.2. In the rest of this section, I will discuss events generated by various user interface components.


Action Events

Action events are generated by several different types of components: Button, Checkbox, Choice, List, and TextField. Action events are handled by the action() method, which has the form:

        public boolean action(Event evt, Object arg) {
           . . . // handle the event
        }

This method should return the boolean true if it processes an event. If it does not process an event, it should ordinarily return super.action(evt,arg). This gives the superclass a chance to handle the event.

To handle action events, you should define the action() method in a subclass of Applet, Panel, or Window. When you write an action() method, you will generally know exactly which components might generate action events for it to handle, so you can write it to handle just those components.

The second parameter in the action() method, arg, contains some information relevant to the event. What type of information arg contains depends on the type of object that generated the event. The first parameter, evt, has an instance variable, evt.target, which tells which component first received the event. For an action event, this is also the component that generated the event in the first place. This means that an action() method often looks something like this:

        public boolean action(Event evt, Object arg) {
           if (evt.target == button1) {
               // handle a click on button1
               return true;
           }
           else if (evt.target == button2) {
               // handle a click on button2
               return true;
           }
           else if (evt.target == colorChoice) {
               // handle a selection from Choice object colorChoice
               return true;
           }
           .
           .   // handle other possible targets
           .
           else 
             return super.action(evt,arg);
        }

(In some cases, you might find it convenient to use the instanceof operator with evt.target to tell what type of object generated the event. In simple cases, when there are only a few components to worry about, you might not even have to check evt.target; the second parameter, arg might contain all the information you need.)

Here are the details about the action events that can be generated by various types of components:


Scrollbar Events

A scroll bar generates an event whenever the value of the scroll bar changes. However, these events are not action events. In fact, in order to handle scroll bar events, you must override the handleEvent() method itself. This method has the form:

public boolean handleEvent(Event evt) { ... }

The instance variable evt.target tells which component first received the event. For scroll bar events, this will be the scroll bar itself. Another instance variable, event.id, tells what type of event this is. Scroll bars produce five different types of events, depending on which part of the scroll bar the user manipulates: SCROLL_LINE_UP, SCROLL_LINE_DOWN, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, and SCROLL_ABSOLUTE. (The last of these events is generated when the user drags the tab of the scroll bar.) And evt.arg contains other information relevant to the event. For scroll bar events, event.arg is an object of type Integer that contains the new value of the scroll bar. (An object of the Integer is a "wrapper" for a value of the primitive type, int. To get the actual int value, you can use ((Integer)(evt.arg)).intValue(). But you could also get the value more easily, by calling the scroll bar's getValue() method.)

If you want to override handleEvent() to handle scroll bar events, it should look something like this. (Assume that you have written a method, doScroll(), to handle scroll bar events.)

      pubic boolean handleEvent(Event evt) {
         if ( evt.id == Event.SCROLL_LINE_UP ||
              evt.id == Event.SCROLL_LINE_DOWN ||
              evt.id == Event.SCROLL_PAGE_UP ||
              evt.id == Event.SCROLL_PAGE_DOWN ||
              evt.id == Event.SCROLL_ABSOLUTE ) {
            doScroll(evt.target);
            return true;  // event was handled
         }
         else if . . .  // maybe handle other events
         else
            return super.handleEvent(evt);
       }

In the case where handleEvent() does not actually handle the event, it is essential for it to return the value super.handleEvent(evt). This calls the handleEvent() method from the superclass of the subclass you are creating. The method from the superclass must be called to handle any events that you do not handle yourself in the subclass.


Other Events

There are just a few more types of events that can show up in the handleEvent() method. I will not discuss them here in detail, but for the record:


An Event-handling Example

To complete this section, I will write a complete applet that uses nested components and that handles a variety of different events. First, here's the applet itself:

Your browser does not support Java;
here is what the applet looks like:


This applet is meant to show how shapes and text of different colors look on backgrounds of different colors. The horizontal scroll bar controls the background color, while the vertical scroll bar controls the color of the shapes and text. The various controls on the bottom determine what shape and text are displayed and whether bright colors or dim colors are used, (To change the text in the display, you have to type the new text in the box and press return.)

First, let's define a class to represent the display area of the applet. Since it will be used for drawing, it should be a subclass of canvas. This definition assumes that the classes from the package java.awt have been imported.

    class ColorCanvas extends Canvas {
       
          // Display a shape and some text.
          // The canvas's setForeground() and setBackground()
          // methods should be called to set the colors to
          // be used for drawing.
       
       public String text; // text to be displayed
       public int shape;   // code for shape to be displayed;
       
       public final static int RECT = 0;  // shape code for a rectangle
       public final static int OVAL = 1;  // shape code for an oval
       public final static int ROUNDED = 2; // shape code for a round rect

       public ColorCanvas() {
           text = "Hello World";  // default text
           shape = RECT;  // default shape
       }
       
       public void paint(Graphics g) {
           int width = size().width;   // get size of canvas
           int height = size().height;
           int shape_left = width / 9;  // compute position and size of shape
           int shape_top = height / 3;
           int shape_width = (7*width / 9);
           int shape_height =  (5*height / 9);
           switch (shape) {   // draw the shape
              case RECT:
                 g.fillRect(shape_left,shape_top,shape_width,shape_height);
                 break;
              case OVAL:
                 g.fillOval(shape_left,shape_top,shape_width,shape_height);
                 break;
              case ROUNDED:
                 g.fillRoundRect(shape_left,shape_top,shape_width,shape_height,16,16);
                 break;
           }
           g.drawString(text,width/9,2*height/9);  // draw the text
       }
       
     }  // end of class ColorCanvas
     

We can then define the applet. The structure of the applet is set up in its init() method. Events are handled in its action() method and in its handleEvent() method.

      import java.awt.*;
      import java.applet.*;

      public class EventDemo extends Applet {
      
         ColorCanvas display;  // display area
         Choice shapeChoice;   // for selecting which shape to display
         Checkbox brightColors;// for selecting bright or dim colors
         TextField text;       // for entering the text to be displayed
         Scrollbar hScroll;    // horizontal scroll bar
         Scrollbar vScroll;    // vertical scroll bar
      
         public void init() {  // set up contents of applet
             
             Panel topPanel = new Panel(); // to hold display and scroll bars
             topPanel.setLayout(new BorderLayout());
             display = new ColorCanvas();
             topPanel.add("Center", display);
             hScroll = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,100);
             topPanel.add("South", hScroll);
             vScroll = new Scrollbar(Scrollbar.VERTICAL,50,1,0,100);
             topPanel.add("East", vScroll);
             
             Panel bottomPanel = new Panel();  // for controls
             bottomPanel.setLayout(new GridLayout(1,3,5,5));
             shapeChoice = new Choice();
             shapeChoice.addItem("Rectangle");
             shapeChoice.addItem("Oval");
             shapeChoice.addItem("RoundRect");
             bottomPanel.add(shapeChoice);
             brightColors = new Checkbox("Bright Colors");
             bottomPanel.add(brightColors);
             text = new TextField("Hello World");
             bottomPanel.add(text);
             
             setLayout(new BorderLayout(5,5));  // applies to applet itself
             add("Center", topPanel);
             add("South", bottomPanel);
             
             setBackground(Color.darkGray);   // background for applet
             setDisplayColors();  // defined below
             
         } // end of init()
         
         public Insets insets() {  // leave border around edge of applet
            return new Insets(5,5,5,5);
         }
         
         public boolean action(Event evt, Object arg) {
            if (evt.target == shapeChoice) {
               // user has selected a shape; set the shape
               // variable in the display, and ask the system
               // to redraw the display 
               switch (shapeChoice.getSelectedIndex()) {
                  case 0:
                     display.shape = ColorCanvas.RECT;
                     break;
                  case 1:
                     display.shape = ColorCanvas.OVAL;
                     break;
                  case 2:
                     display.shape = ColorCanvas.ROUNDED;
                     break;
               }
               display.repaint();
            }
            else if (evt.target == brightColors) {
               // user has changed the state of the checkbox;
               // reset the colors for the display,
               // and ask the system to redraw the display
               setDisplayColors();
               display.repaint();
            }
            else if (evt.target == text) {
               // user has entered new text in the text field
               // and has pressed return; set the corresponding
               // variable in the display, and ask system to redraw it
               display.text = text.getText();
               display.repaint();
            }
            return true;
         } // end of action()
         
         public boolean handleEvent(Event evt) {
            if ( evt.id == Event.SCROLL_LINE_UP ||
                 evt.id == Event.SCROLL_LINE_DOWN ||
                 evt.id == Event.SCROLL_PAGE_UP ||
                 evt.id == Event.SCROLL_PAGE_DOWN ||
                 evt.id == Event.SCROLL_ABSOLUTE ) {
               // user has changed the value of one of the
               // scroll bars;  adjust the colors in the display
               // and repaint it.  (I don't have to check
               // which scroll bar it is, because setDisplayColors()
               // always checks the values of both scroll bars.)
               setDisplayColors();
               Graphics g = display.getGraphics();
               display.update(g); // call update() for immediate
               g.dispose();       // repainting, while the user is scrolling
               return true;
            }
            else
               return super.handleEvent(evt);
         } // end of handleEvent()
               
         void setDisplayColors() {
              // set foreground and background colors of display,
              // depending on values of scroll bars and
              // on state of the checkbox.  (Colors are made
              // using Color.getHSBColor(float,float,float),
              // which creates a color given a hue, a saturation,
              // and a brightness.  The parameters must be between
              // 0.0 and 1.0.)
            float backgroundHue = hScroll.getValue() / 100.0F;
            float foregroundHue = vScroll.getValue() / 100.0F;
            float saturation = 1.0F;
            float brightness;
            if (brightColors.getState())
               brightness = 1.0F;
            else 
               brightness = 0.6F;
            Color backgroundColor = 
                   Color.getHSBColor(backgroundHue,saturation,brightness);
            Color foregroundColor = 
                   Color.getHSBColor(foregroundHue,saturation,brightness);
            display.setBackground(backgroundColor);
            display.setForeground(foregroundColor);
         } // end of setDisplayColors()
         
      } // end of class Event Demo

[ Next Section | Previous Section | Chapter Index | Main Index ]