Introduction
to Interactive Programming
by Lynn Andrea
Stein
A Rethinking
CS101 Project
- How do we design an entity to emphasize its responses to various events?
In previous chapters, we have seen how an animate object can use its explicit control loop as a dispatcher, calling appropriate methods depending on what input it receives. In this chapter, we discuss a style of programming that shifts the emphasis from the dispatcher to the various handler methods called by that control loop. Entities designed in this way highlight their responses to a variety of situations, now called events. An implicit -- behind-the-scenes -- control loop dispatches to these event handler methods.
This is event-driven style of programming is very commonly used in graphical user interfaces (GUIs). In Java, AWT's paint methods are an example of this kind of event-driven programming. This chapter explores a portion of the java.awt package, including java.awt.Component and its subclasses, to illustrate the structure of programs written in an event-driven style.
Recall the central loop in the Calculator, which had the form:
public void run() {while (true) {int buttonID = gui.getButton(); switch (buttonID) { case Calculator.OP_ADD : case Calculator.OP_SUB : case Calculator.OP_MUL : case Calculator.OP_DIV :handleOp( buttonID ); break;case Calculator.EQUALS :handleEquals( buttonID ); break;case Calculator.DOT :handleDot( buttonID ); break;case Calculator.CLEAR :handleClear( buttonID ); break;default :if ((0 <= buttonID ) && ( buttonID <= 9 )) { handleNum( buttonID ); } else { error( "No such button " + buttonID ); }} // switch} // while} // run
If you wrote your ButtonHandler this way, the meat of your code was in the handler methods. Emphasizing the handlers leads to a style of programming, called event driven programming, very common in user interfaces.
The idea behind event driven programming is that there is a(n often implicit) central control loop much like the ButtonHandler's, whose sole job is to recognize that something has happened (an event) and to alert the appropriate handler method. All of the interesting activity then takes place in these handlers. So event driven programming is a lot like the interactive control loops we've been working with -- concurrent, ongoing, condition-monitoring....-- but with the loop itself hidden and the focus shifted to the handlers.
For example, we might have a convention that a graphical user interface (GUI) component -- like a window or a dialog box or a checkbox -- should have a paint method. Every time that something happened that would cause that GUI component to need repainting, Java would automagically call that component's paint method. One example of this might be when a window that was previously covering your checkbox gets moved so that your checkbox becomes visible again. Another example might be the first time that your Dialog appears.
This paint method actually exists, and all of the visible objects in Java's GUI package, java.awt, implement it. Java really does call paint all by itself; in fact, you should never call a GUI component's paint method directly.
Let's look at java.awt now.
The java.awt package contains the major classes for making graphical user interfaces. These include the subclasses of java.awt.Component, the java.awt.Graphics class, and the java.awt.Event class together with the java.awt.Event package. We'll come back and look at AWT Events; for now, we are going to focus on Components and, in a bit, Graphics.
The parent of all at component classes is java.awt.Component. The Component class embodies a screen presence. You can't have a vanilla Component, though; you can only have an instance of one of its subclasses. (In fact, java.awt.Component is an abstract class. See the sidebar for further detail.)
First, though, let's talk about other Component types. There is a generic Component, called Canvas, that you can instantiate. The java.awt.Canvas class doesn't do anything special, but you can either use it as a generic component or extend it to get specialized behavior. For example, you could override the Canvas's paint method and make the Canvas draw a particular picture. (See details on paint and Graphics, below.)
There are also Component classes suitable for collecting together other Components. For example, one GUI component might have several buttons in it. Each of these holder components extends java.awt.Container. Like Component, Container is abstract, meaning you can't instantiate it directly. The most common subclasses of Container to use are Panel, Frame, and Dialog. Panel is a generic container component. It can't be used on its own, though; it needs a Container to contain it. Applets are a special kind (subclass) of Panel: an Applet runs either in a specialized container like Java's appletviewer or inside a web browser. Frame is a top level standalone Container; you could put a Panel or an Applet inside a Frame. A Dialog is a pretty standard Dialog box....
To put Components inside Containers, you call the Container's add(Component c) method. If this is the top level Container (e.g., the Frame or even the Applet), you may also need to call the Container's show() method to make it appear on the screen. Once a Component has been added to the Container, the Container knows that it is responsible for the Component. For example, when you call the Container's paint method, it will by default call the paint method of each of its contained Components. (If some of those Components are themselves Containers, they, too, will call their contents' paint methods....)
If you are doing sophisticated graphical layout, you will want to check out java.awt's Layout classes. These allow you to add components in aesthetically pleasing ways.
When it comes time to paint a component (container or otherwise), Java automagically calls that component's paint method. The paint method takes a Graphics context as an argument. A Graphics is a thing that knows how to make pictures appear. You cannot, in general, supply the appropriate Graphics context to a Component; instead, Java's behind the scenes bookkeeping takes care of this. (Remember, paint(Graphics g) is used in event-driven style; that is, it is called by Java, not by your program.)
Suppose that you want to have your Component contain a rectangle in the upper left-hand corner. A Graphics has a drawRect method which does just that. So when your component's paint method is called, it should ask whatever Graphics object is supplied to it to drawRect( int x, int y, int width, int height). (drawRect takes four arguments: the upper left hand coordinates and the size coordinates. All measurements are in pixels -- tiny boxes that make up your screen -- and the origin -- the point (0,0) -- is in the upper left-hand corner of the component. These are called "screen coordinates".) Graphics objects have lots of other drawing methods, too. See the java.awt.Graphics documentation for a comprehensive listing.
If you always let Java supply you with the Graphics object, you will find that your coordinates are automatically translated appropriately. You shouldn't try to hang onto that Graphics, though. Each time that Java calls your paint method, it will supply an appropriate Graphics. It will always be there whenever you need it. But you have to let Java take care of it. This is like asking a librarian to file a book. Every time that you make the request, you must actually hand the librarian the book. This way, s/he can always check out the details of the book before filing it. It doesn't make sense to ask the librarian to file a book without handing that book to the librarian. This means that the librarian-book-filing rule can always make reference to "the book I'm filing", even though the particular book won't appear until filing time. If the librarian made the rule relative to one book -- Charlotte's Web, say -- then the rule wouldn't apply properly next time, when the librarian had to file The Phantom Tollbooth.
One final note on painting:
If you do ever need to tell the system that you want your component to be painted, you need to arrange for Java to provide the appropriate information to your class. You can do this by calling the component's repaint() method. Unlike paint, which takes a Graphics as an argument, repaint takes no parameters. (This is good, because you don't generally have a Graphics around to give paint. This is another thing that Java keeps track of automagically.) You don't have to implement repaint(); java.awt.Component.repaint(), which you will inherit, queues up a new paint(Graphics g) request (even supplying the appropriate Graphics) behind the scenes. Remember: You never call paint, and you never implement repaint. To cause a painting to happen, call repaint(); to explain how to paint your component, implement (override) the paint(Graphics g) method -- and don't worry about the Graphics, it will be automatically supplied to you!
Paint is among the simplest kind of events that exist in Java. The main idea is that, instead of sitting around inside a loop, waiting for something to happen, an event-driven component is called only when something happens. The "loop" is hidden inside the system.
In Java, the java.awt package implements this kind of event handling for user interface events. These are things like mouse clicks and motion, keyboard presses, etc., as well has "higher level" events like clicking on a button or closing a window. Java supplies a number of event handler methods (in certain classes) that you can implement or override. These are called automagically by Java whenever the appropriate event occurs.
For example, every java.awt.Component has a processEvent method. This method takes one argument, an Event. This is supplied by Java, just like the Graphics argument to paint. That is, it is given to the method; you don't have to supply it. And, like the Graphics, the Event contains most of the information you'd want in order to appropriately process the event. For example, an Event generated by moving the mouse will have x and y fields that refer to the new location of the mouse (in screen coordinates).
Specifically, there are a whole bunch of Event classes that inherit from java.awt.Event. One, java.awt.AWTEvent, is the parent of the AWT event classes. The AWT event classes are in their own package, java.awt.event. They are java.awt.event.ActionEvent, AdjustmentEvent, ItemEvent, TextEvent, ComponentEvent, ContainerEvent, FocusEvent, PaintEvent, WindowEvent, KeyEvent, and MouseEvent. The PaintEvent type is what causes the repaint/paint behavior described above, although it happens entirely behind the scenes. MouseEvents include things like mouse motion or click events. KeyEvents are triggered by the user's typing. ActionEvents are used to handle some of the more sophisticated GUI widgets -- buttons, checkboxes, and the like. They enable you to deal in high-level conceptual actions such as "the box was checked" rather than in individual mouse clicks, though the mouse clicks (and MouseEvents) exist behind the scenes. Each of the major awt event types also has a specialized process...Event method, e.g., processActionEvent( ActionEvent e ); these methods belong to java.awt.Component and can be overridden to produce specialized effects, much like with paint. However, that is not the recommended way to deal with awt events.
In a later chapter on event delegation, we will see some additional wrinkles of the java.awt event model.
This course is a part of Lynn Andrea Stein's Rethinking CS101 project at the MIT AI Lab and the Department of Electrical Engineering and Computer Science at the Massachusetts Institute of Technology.
Questions or comments: