Wednesday, January 14, 2009
Tuesday, January 6, 2009
Even Handling
Chapter 8. Event Handling
Basics of Event Handling
The AWT Event Hierarchy
Semantic and Low-Level Events in the AWT
Low-Level Event Types
Actions
Multicasting
Implementing Event Sources
Event handling is of fundamental importance to programs with a graphical user interface. To implement user interfaces, you must master the way in which Java handles events. This chapter explains how the Java AWT event model works. You will see how to capture events from the mouse and the keyboard. This chapter also shows you how to use the simplest GUI elements, such as buttons. In particular, this chapter discusses how to work with the basic events generated by these components. The next chapter shows you how to put together the most common of the components that Swing offers, along with a detailed coverage of the events they generate.
NOTE
Java 1.1 introduced a much improved event model for GUI programming. We do not cover the deprecated 1.0 event model in this chapter.
Basics of Event Handling
Any operating environment that supports GUIs constantly monitors events such as keystrokes or mouse clicks. The operating environment reports these events to the programs that are running. Each program then decides what, if anything, to do in response to these events. In languages like Visual Basic, the correspondence between events and code is obvious. One writes code for each specific event of interest and places the code in what is usually called an event procedure. For example, a Visual Basic button named HelpButton would have a HelpButton_Click event procedure associated with it. The code in this procedure executes whenever that button is clicked. Each Visual Basic GUI component responds to a fixed set of events, and it is impossible to change the events to which a Visual Basic component responds.
On the other hand, if you use a language like raw C to do event-driven programming, you need to write the code that constantly checks the event queue for what the operating environment is reporting. (You usually do this by encasing your code in a giant loop with a massive switch statement!) This technique is obviously rather ugly, and, in any case, it is much more difficult to code. The advantage is that the events you can respond to are not as limited as in languages, like Visual Basic, that go to great lengths to hide the event queue from the programmer.
The Java programming environment takes an approach somewhat between the Visual Basic approach and the raw C approach in terms of power and, therefore, in resulting complexity. Within the limits of the events that the AWT knows about, you completely control how events are transmitted from the event sources (such as buttons or scrollbars) to event listeners. You can designate any object to be an event listener—in practice, you pick an object that can conveniently carry out the desired response to the event. This event delegation model gives you much more flexibility than is possible with Visual Basic, in which the listener is predetermined, but it requires more code and is more difficult to untangle (at least until you get used to it).
Event sources have methods that allow you to register event listeners with them. When an event happens to the source, the source sends a notification of that event to all the listener objects that were registered for that event.
As one would expect in an object-oriented language like Java, the information about the event is encapsulated in an event object. In Java, all event objects ultimately derive from the class java.util.EventObject. Of course, there are subclasses for each event type, such as ActionEvent and WindowEvent.
Different event sources can produce different kinds of events. For example, a button can send ActionEvent objects, whereas a window can send WindowEvent objects.
To sum up, here's an overview of how event handling in the AWT works.
A listener object is an instance of a class that implements a special interface called (naturally enough) a listener interface.
An event source is an object that can register listener objects and send them event objects.
The event source sends out event objects to all registered listeners when that event occurs.
The listener objects will then use the information in the event object to determine their reaction to the event.
You register the listener object with the source object by using lines of code that follow the model
eventSourceObject.addEventListener(eventListenerObject);
Here is an example:ActionListener listener = . . .;
JButton button = new JButton("Ok");
button.addActionListener(listener);
Now the listener object is notified whenever an "action event" occurs in the button. For buttons, as you might expect, an action event is a button click.
Code like the above requires that the class to which the listener object belongs implements the appropriate interface (which in this case is the ActionListener interface). As with all interfaces in Java, implementing an interface means supplying methods with the right signatures. To implement the ActionListener interface, the listener class must have a method called actionPerformed that receives an ActionEvent object as a parameter.class MyListener implements ActionListener
{
. . .
public void actionPerformed(ActionEvent event)
{
// reaction to button click goes here
. . .
}
}
Whenever the user clicks the button, the JButton object creates an ActionEvent object and calls listener.actionPerformed(event), passing that event object. It is possible for multiple objects to be added as listeners to an event source such as a button. In that case, the button calls the actionPerformed methods of all listeners whenever the user clicks the button.
Figure 8-1 shows the interaction between the event source, event listener, and event object.
Figure 8-1. Event notification
NOTE
In this chapter, we put particular emphasis on event handling for user interface events such as button clicks and mouse moves. However, the basic event handling architecture is not limited to user interfaces. As you will see in the next chapter, objects that are not user interface components can also send event notifications to listeners—usually to tell them that a property of the object has changed.
Example: Handling a Button Click
As a way of getting comfortable with the event delegation model, let's work through all details needed for the simple example of responding to a button click. For this example, we will want
A panel populated with three buttons; and
Three listener objects that are added as action listeners to the buttons.
With this scenario, each time a user clicks on any of the buttons on the panel, the associated listener object then receives an ActionEvent that indicates a button click. In our sample program, the listener object will then change the background color of the panel.
Before we can show you the program that listens to button clicks, we first need to explain how to create buttons and how to add them to a panel. (For more on GUI elements, see Chapter 9.)
You create a button by specifying a label string, an icon, or both in the button constructor. Here are two examples:JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));
Adding buttons to a panel occurs through a call to a method named (quite mnemonically) add. The add method takes as a parameter the specific component to be added to the container. For example,class ButtonPanel extends JPanel
{
public ButtonPanel()
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
}
}
Figure 8-2 shows the result.
Figure 8-2. A panel filled with buttons
Now that you know how to add buttons to a panel, you'll need to add code that lets the panel listen to these buttons. This requires classes that implement the ActionListener interface, which, as we just mentioned, has one method: actionPerformed, whose signature looks like this:public void actionPerformed(ActionEvent event)
NOTE
The ActionListener interface we used in the button example is not restricted to button clicks. It is used in many separate situations:
When an item is selected from a list box with a double click;
When a menu item is selected;
When the ENTER key is clicked in a text field;
When a certain amount of time has elapsed for a Timer component.
You will see more details in this chapter and the next.
The way to use the ActionListener interface is the same in all situations: the actionPerformed method (which is the only method in ActionListener) takes an object of type ActionEvent as a parameter. This event object gives you information about the event that happened.
When a button is clicked, then we want to set the background color of the panel to a particular color. We store the desired color in our listener class.class ColorAction implements ActionListener
{
public ColorAction(Color c)
{
backgroundColor = c;
}
public void actionPerformed(ActionEvent event)
{
// set panel background color
. . .
}
private Color backgroundColor;
}
We then construct one object for each color and set the objects as the button listeners.ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction blueAction = new ColorAction(Color.BLUE);
ColorAction redAction = new ColorAction(Color.RED);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
For example, if a user clicks on the button marked "Yellow," then the actionPerformed method of the yellowAction object is called. Its backgroundColor instance field is set to Color.YELLOW, and it can now proceed to set the panel's background color.
Just one issue remains. The ColorAction object doesn't have access to the panel variable. You can solve this problem in two ways. You can store the panel in the ColorAction object and set it in the ColorAction constructor. Or, more conveniently, you can make ColorAction into an inner class of the ButtonPanel class. Its methods can then access the outer panel automatically. (For more information on inner classes, see Chapter 6.)
We follow the latter approach. Here is how you place the ColorAction class inside the ButtonPanel class.
class ButtonPanel extends JPanel{ . . . private class ColorAction implements ActionListener{ . . . public void actionPerformed(ActionEvent event) { setBackground(backgroundColor); // i.e., outer.setBackground(...) } private Color backgroundColor; }}
Look closely at the actionPerformed method. The ColorAction class doesn't have a setBackground method. But the outer ButtonPanel class does. The methods are invoked on the ButtonPanel object that constructed the inner class objects. (Note again that outer is not a keyword in the Java programming language. We just use it as an intuitive symbol for the invisible outer class reference in the inner class object.)
This situation is very common. Event listener objects usually need to carry out some action that affects other objects. You can often strategically place the listener class inside the class whose state the listener should modify.
Example 8-1 contains the complete program. Whenever you click one of the buttons, the appropriate action listener changes the background color of the panel.
Example 8-1. ButtonTest.java 1. import java.awt.*;
2. import java.awt.event.*;
3. import javax.swing.*;
4.
5. public class ButtonTest
6. {
7. public static void main(String[] args)
8. {
9. ButtonFrame frame = new ButtonFrame();
10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11. frame.setVisible(true);
12. }
13. }
14.
15. /**
16. A frame with a button panel
17. */
18. class ButtonFrame extends JFrame
19. {
20. public ButtonFrame()
21. {
22. setTitle("ButtonTest");
23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25. // add panel to frame
26.
27. ButtonPanel panel = new ButtonPanel();
28. add(panel);
29. }
30.
31. public static final int DEFAULT_WIDTH = 300;
32. public static final int DEFAULT_HEIGHT = 200;
33. }
34.
35. /**
36. A panel with three buttons.
37. */
38. class ButtonPanel extends JPanel
39. {
40. public ButtonPanel()
41. {
42. // create buttons
43.
44. JButton yellowButton = new JButton("Yellow");
45. JButton blueButton = new JButton("Blue");
46. JButton redButton = new JButton("Red");
47.
48. // add buttons to panel
49.
50. add(yellowButton);
51. add(blueButton);
52. add(redButton);
53.
54. // create button actions
55.
56. ColorAction yellowAction = new ColorAction(Color.YELLOW);
57. ColorAction blueAction = new ColorAction(Color.BLUE);
58. ColorAction redAction = new ColorAction(Color.RED);
59.
60. // associate actions with buttons
61.
62. yellowButton.addActionListener(yellowAction);
63. blueButton.addActionListener(blueAction);
64. redButton.addActionListener(redAction);
65. }
66.
67. /**
68. An action listener that sets the panel's background color.
69. */
70. private class ColorAction implements ActionListener
71. {
72. public ColorAction(Color c)
73. {
74. backgroundColor = c;
75. }
76.
77. public void actionPerformed(ActionEvent event)
78. {
79. setBackground(backgroundColor);
80. }
81.
82. private Color backgroundColor;
83. }
84. }
javax.swing.JButton 1.2
JButton(String label)
constructs a button. The label string can be plain text, or, starting with JDK 1.3, HTML; for example, "Ok".
Parameters:
label
The text you want on the face of the button
JButton(Icon icon)
constructs a button.
Parameters:
icon
The icon you want on the face of the button
JButton(String label, Icon icon)
constructs a button.
Parameters:
label
The text you want on the face of the button
icon
The icon you want on the face of the buttonjava.awt.Container 1.0
Component add(Component c)
adds the component c to this container.javax.swing.ImageIcon 1.2
ImageIcon(String filename)
constructs an icon whose image is stored in a file. The image is automatically loaded with a media tracker (see Chapter 7).
Becoming Comfortable with Inner Classes
Some people dislike inner classes because they feel that a proliferation of classes and objects makes their programs slower. Let's have a look at that claim. You don't need a new class for every user interface component. In our example, all three buttons share the same listener class. Of course, each of them has a separate listener object. But these objects aren't large. They each contain a color value and a reference to the panel. And the traditional solution, with if . . . else statements, also references the same color objects that the action listeners store, just as local variables and not as instance fields.
We believe the time has come to get used to inner classes. We recommend that you use dedicated inner classes for event handlers rather than turning existing classes into listeners. We think that even anonymous inner classes have their place.
Here is a good example of how anonymous inner classes can actually simplify your code. If you look at the code of Example 8-1, you will note that each button requires the same treatment:
1.
Construct the button with a label string.
2.
Add the button to the panel.
3.
Construct an action listener with the appropriate color.
4.
Add that action listener.
Let's implement a helper method to simplify these tasks:void makeButton(String name, Color backgroundColor)
{
JButton button = new JButton(name);
add(button);
ColorAction action = new ColorAction(backgroundColor);
button.addActionListener(action);
}
Then the ButtonPanel constructor simply becomespublic ButtonPanel()
{
makeButton("yellow", Color.YELLOW);
makeButton("blue", Color.BLUE);
makeButton("red", Color.RED);
}
Now you can make a further simplification. Note that the ColorAction class is only needed once: in the makeButton method. Therefore, you can make it into an anonymous class:void makeButton(String name, final Color backgroundColor)
{
JButton button = new JButton(name);
add(button);
button.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
setBackground(backgroundColor);
}
});
}
The action listener code has become quite a bit simpler. The actionPerformed method simply refers to the parameter variable backgroundColor. (As with all local variables that are accessed in the inner class, the parameter needs to be declared as final.)
No explicit constructor is needed. As you saw in Chapter 6, the inner class mechanism automatically generates a constructor that stores all local final variables that are used in one of the methods of the inner class.
TIP
Anonymous inner classes can look confusing. But you can get used to deciphering them if you train your eyes to glaze over the routine code, like this:button.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
setBackground(backgroundColor);
}
});
That is, the button action sets the background color. As long as the event handler consists of just a few statements, we think this can be quite readable, particularly if you don't worry about the inner class mechanics.
TIP
JDK 1.4 introduces a mechanism that lets you specify simple event listeners without programming inner classes. For example, suppose you have a button labeled Load whose event handler contains a single method call:frame.loadData();
Of course, you can use an anonymous inner class:loadButton.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
frame.loadData();
}
});
But the EventHandler class can create such a listener automatically, with the callEventHandler.create(ActionListener.class, frame, "loadData")
Of course, you still need to install the handler:loadButton.addActionListener((ActionListener)
EventHandler.create(ActionListener.class, frame, "loadData"));
The cast is necessary because the create method returns an Object. Perhaps a future version of the JDK will make use of generic types to make this method even more convenient.
If the event listener calls a method with a single parameter that is derived from the event handler, then you can use another form of the create method. For example, the callEventHandler.create(ActionListener.class, frame, "loadData", "source.text")
is equivalent tonew ActionListener()
{
public void actionPerformed(ActionEvent event)
{
frame.loadData(((JTextField) event.getSource()).getText());
}
}
Note that the event handler turns the names of the properties source and text into method calls getSource and getText, using the JavaBeans convention. (For more information on properties and JavaBeans components, please turn to Volume 2.)
However, in practice, this situation is not all that common, and there is no mechanism for supplying parameters that aren't derived from the event object.
Turning Components into Event Listeners
You are completely free to designate any object of a class that implements the ActionListener interface as a button listener. We prefer to use objects of a new class that was expressly created for carrying out the desired button actions. However, some programmers are not comfortable with inner classes and choose a different strategy. They locate the component that changes as a result of the event, make that component implement the ActionListener interface, and add an actionPerformed method. In our example, you can turn the ButtonPanel into an action listener:class ButtonPanel extends JPanel implements ActionListener
{
. . .
public void actionPerformed(ActionEvent event)
{
// set background color
. . .
}
}
Then the panel sets itself as the listener to all three buttons:yellowButton.addActionListener(this);
blueButton.addActionListener(this);
redButton.addActionListener(this);
Note that now the three buttons no longer have individual listeners. They share a single listener object, namely, the button panel. Therefore, the actionPerformed method must figure out which button was clicked.
The getSource method of the EventObject class, the superclass of all other event classes, will tell you the source of every event. The event source is the object that generated the event and notified the listener:Object source = event.getSource();
The actionPerformed method can then check which of the buttons was the source:if (source == yellowButton) . . .
else if (source == blueButton) . . .
else if (source == redButton ) . . .
Of course, this approach requires that you keep references to the buttons as instance fields in the surrounding panel.
As you can see, turning the button panel into the action listener isn't really any simpler than defining an inner class. It also becomes really messy when the panel contains multiple user interface elements.
CAUTION
Some programmers use a different way to find out the event source in a listener object that is shared among multiple sources. The ActionEvent class has a getActionCommand method that returns the command string associated with this action. For buttons, it turns out that the command string defaults to being the button label. If you take this approach, an actionPerformed method contains code like this:String command = event.getActionCommand();
if (command.equals("Yellow")) . . .;
else if (command.equals("Blue")) . . .;
else if (command.equals("Red")) . . .;
We suggest that you do not follow this approach. Relying on the button strings is dangerous. It is an easy mistake to label a button "Gray" and then spell the string slightly differently in the test:if (command.equals("Grey")) . . .
Button strings give you grief when the time comes to internationalize your application. To make the German version with button labels "Gelb," "Blau," and "Rot," you have to change both the button labels and the strings in the actionPerformed method.java.util.EventObject 1.1
Object getSource()
returns a reference to the object where the event occurred.java.awt.event.ActionEvent 1.1
String getActionCommand()
returns the command string associated with this action event. If the action event originated from a button, the command string equals the button label, unless it has been changed with the setActionCommand method.java.beans.EventHandler 1.4
static Object create(Class listenerInterface, Object target, String action)
static Object create(Class listenerInterface, Object target, String action, String eventProperty)
static Object create(Class listenerInterface, Object target, String action, String eventProperty, String listenerMethod)
construct an object of a proxy class that implements the given interface. Either the named method or all methods of the interface carry out the given action on the target object.
The action can be a method name or a property of the target. If it is a property, its setter method is executed. For example, an action "text" is turned into a call of the setText method.
The event property consists of one or more dot-separated property names. The first property is read from the parameter of the listener method. The second property is read from the resulting object, and so on. The final result becomes the parameter of the action. For example, the property "source.text" is turned into calls to the getSource and getText methods.
Example: Changing the Look and Feel
By default, Swing programs use the Metal look and feel. There are two ways to change to a different look and feel. The first way is to supply a file swing.properties in the jre/lib subdirectory of your Java installation. In that file, set the property swing.defaultlaf to the class name of the look and feel that you want. For example,swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel
Note that the Metal look and feel is located in the javax.swing package. The other look-and-feel packages are located in the com.sun.java package and need not be present in every Java implementation. Currently, for copyright reasons, the Windows and Mac look-and-feel packages are only shipped with the Windows and Mac versions of the Java runtime environment.
TIP
Here is a useful tip for testing. Because lines starting with a # character are ignored in property files, you can supply several look and feel selections in the swing.properties file and move around the # to select one of them:#swing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel
swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel
#swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel
You must restart your program to switch the look and feel in this way. A Swing program reads the swing.properties file only once, at startup.
The second way is to change the look and feel dynamically. Call the static UIManager.setLookAndFeel method and give it the name of the look-and-feel class that you want. Then call the static method SwingUtilities.updateComponentTreeUI to refresh the entire set of components. You need to supply one component to that method; it will find all others. The UIManager.setLookAndFeel method may throw a number of exceptions when it can't find the look and feel that you request, or when there is an error loading it. As always, we ask you to gloss over the exception handling code and wait until Chapter 11 for a full explanation.
Here is an example showing how you can switch to the Motif look and feel in your program:String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
try
{
UIManager.setLookAndFeel(plaf);
SwingUtilities.updateComponentTreeUI(panel);
}
catch(Exception e) { e.printStackTrace(); }
To enumerate all installed look and feel implementations, callUIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
Then you can get the name and class name for each look and feel asString name = infos[i].getName();
String className = infos[i].getClassName();
Example 8-2 is a complete program that demonstrates how to switch the look and feel (see Figure 8-3). The program is similar to Example 8-1. Following the advice of the preceding section, we use a helper method makeButton and an anonymous inner class to specify the button action, namely, to switch the look and feel.
Figure 8-3. Switching the look and feel
There is one fine point to this program. The actionPerformed method of the inner action listener class needs to pass the this reference of the outer PlafPanel class to the updateComponentTreeUI method. Recall from Chapter 6 that the outer object's this pointer must be prefixed by the outer class name:SwingUtilities.updateComponentTreeUI(PlafPanel.this);
Example 8-2. PlafTest.java 1. import java.awt.*;
2. import java.awt.event.*;
3. import javax.swing.*;
4.
5. public class PlafTest
6. {
7. public static void main(String[] args)
8. {
9. PlafFrame frame = new PlafFrame();
10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11. frame.setVisible(true);
12. }
13. }
14.
15. /**
16. A frame with a button panel for changing look and feel
17. */
18. class PlafFrame extends JFrame
19. {
20. public PlafFrame()
21. {
22. setTitle("PlafTest");
23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25. // add panel to frame
26.
27. PlafPanel panel = new PlafPanel();
28. add(panel);
29. }
30.
31. public static final int DEFAULT_WIDTH = 300;
32. public static final int DEFAULT_HEIGHT = 200;
33. }
34.
35. /**
36. A panel with buttons to change the pluggable look and feel
37. */
38. class PlafPanel extends JPanel
39. {
40. public PlafPanel()
41. {
42. UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
43. for (UIManager.LookAndFeelInfo info : infos)
44. makeButton(info.getName(), info.getClassName());
45. }
46.
47. /**
48. Makes a button to change the pluggable look and feel.
49. @param name the button name
50. @param plafName the name of the look and feel class
51. */
52. void makeButton(String name, final String plafName)
53. {
54. // add button to panel
55.
56. JButton button = new JButton(name);
57. add(button);
58.
59. // set button action
60.
61. button.addActionListener(new
62. ActionListener()
63. {
64. public void actionPerformed(ActionEvent event)
65. {
66. // button action: switch to the new look and feel
67. try
68. {
69. UIManager.setLookAndFeel(plafName);
70. SwingUtilities.updateComponentTreeUI(PlafPanel.this);
71. }
72. catch(Exception e) { e.printStackTrace(); }
73. }
74. });
75. }
76. }
javax.swing.UIManager 1.2
static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels()
gets an array of objects that describe the installed look-and-feel implementations.
static setLookAndFeel(String className)
sets the current look and feel.
Parameters:
className
the name of the look-and-feel implementation classjavax.swing.UIManager.LookAndFeelInfo 1.2
String getName()
returns the display name for the look and feel.
String getClassName()
returns the name of the implementation class for the look and feel.
Example: Capturing Window Events
Not all events are as simple to handle as button clicks. Here is an example of a more complex case that we already briefly noted in Chapter 7. Before the appearance of the EXIT_ON_CLOSE option in the JDK 1.3, programmers had to manually exit the program when the main frame was closed. In a non-toy program, you will want to do that as well because you want to close the program only after you check that the user won't lose work. For example, when the user closes the frame, you may want to put up a dialog to warn the user if unsaved work is about to be lost and to exit the program only when the user agrees.
When the program user tries to close a frame window, the JFrame object is the source of a WindowEvent. If you want to catch that event, you must have an appropriate listener object and add it to the list of window listeners.WindowListener listener = . . .;
frame.addWindowListener(listener);
The window listener must be an object of a class that implements the WindowListener interface. There are actually seven methods in the WindowListener interface. The frame calls them as the responses to seven distinct events that could happen to a window. The names are self-explanatory, except that "iconified" is usually called "minimized" under Windows. Here is the complete WindowListener interface:public interface WindowListener
{
void windowOpened(WindowEvent e);
void windowClosing(WindowEvent e);
void windowClosed(WindowEvent e);
void windowIconified(WindowEvent e);
void windowDeiconified(WindowEvent e);
void windowActivated(WindowEvent e);
void windowDeactivated(WindowEvent e);
}
NOTE
To find out whether a window has been maximized, install a WindowStateListener. See the following API notes on page 302 for details.
As is always the case in Java, any class that implements an interface must implement all its methods; in this case, that means implementing seven methods. Recall that we are only interested in one of these seven methods, namely, the windowClosing method.
Of course, we can define a class that implements the interface, add a call to System.exit(0) in the windowClosing method, and write do-nothing functions for the other six methods:class Terminator implements WindowListener
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
public void windowOpened(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
}
Adapter Classes
Typing code for six methods that don't do anything is the kind of tedious busywork that nobody likes. To simplify this task, each of the AWT listener interfaces that has more than one method comes with a companion adapter class that implements all the methods in the interface but does nothing with them. For example, the WindowAdapter class has seven do-nothing methods. This means the adapter class automatically satisfies the technical requirements that Java imposes for implementing the associated listener interface. You can extend the adapter class to specify the desired reactions to some, but not all, of the event types in the interface. (An interface such as ActionListener that has only a single method does not need an adapter class.)
Let us make use of the window adapter. We can extend the WindowAdapter class, inherit six of the do-nothing methods, and override the windowClosing method:class Terminator extends WindowAdapter
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}
Now you can register an object of type Terminator as the event listener:WindowListener listener = new Terminator();
frame.addWindowListener(listener);
Whenever the frame generates a window event, it passes it to the listener object by calling one of its seven methods (see Figure 8-4). Six of those methods do nothing; the windowClosing method calls System.exit(0), terminating the application.
Figure 8-4. A window listener
CAUTION
If you misspell the name of a method when extending an adapter class, then the compiler won't catch your error. For example, if you define a method windowIsClosing in a WindowAdapter class, then you get a class with eight methods, and the windowClosing method does nothing.
Creating a listener class that extends the WindowAdapter is an improvement, but we can go even further. There is no need to give a name to the listener object. Simply writeframe.addWindowListener(new Terminator());
But why stop there? We can make the listener class into an anonymous inner class of the frame.frame.addWindowListener(new
WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
This code does the following:
Defines a class without a name that extends the WindowAdapter class;
Adds a windowClosing method to that anonymous class (as before, this method exits the program);
Inherits the remaining six do-nothing methods from WindowAdapter;
Creates an object of this class; that object does not have a name, either;
Passes that object to the addWindowListener method.
We say again that the syntax for using anonymous inner classes takes some getting used to. The payoff is that the resulting code is as short as possible.java.awt.event.WindowListener 1.1
void windowOpened(WindowEvent e)
is called after the window has been opened.
void windowClosing(WindowEvent e)
is called when the user has issued a window manager command to close the window. Note that the window will close only if its hide or dispose method is called.
void windowClosed(WindowEvent e)
is called after the window has closed.
void windowIconified(WindowEvent e)
is called after the window has been iconified.
void windowDeiconified(WindowEvent e)
is called after the window has been deiconified.
void windowActivated(WindowEvent e)
is called after the window has become active. Only a frame or dialog can be active. Typically, the window manager decorates the active window, for example, by highlighting the title bar.
void windowDeactivated(WindowEvent e)
is called after the window has become deactivated.java.awt.event.WindowStateListener 1.4
void windowStateChanged(WindowEvent event)
is called after the window has been maximized, iconified, or restored to its normal size.java.awt.event.WindowEvent 1.1
int getNewState() 1.4
int getOldState() 1.4
return the new and old state of a window in a window state change event. The returned integer is one of the following values:Frame.NORMAL
Frame.ICONIFIED
Frame.MAXIMIZED_HORIZ
Frame.MAXIMIZED_VERT
Frame.MAXIMIZED_BOTH
Monday, January 5, 2009
data structure
1
Winter 2003 1
• B+ Trees. Section 10.3-10.6, 10.8
• Static and Extendible Hashing. Section 11.1 and 11.2
Winter 2003 2
• Inserts and deletes affect only the leaf pages. As a
consequence, long overflow chains at the leaf pages
may result
• Long overflow chains may significantly slow down the
time to retrieve a record (all overflow pages have to be
searched)
• Overflow pages are not sorted. Therefore, lookup is
slow. Possible to keep overflow pages sorted also but
inserts will be slow
• Overflow pages do not go away unless all records in the
overflow pages are deleted, or a complete reorganization
is performed
2
Winter 2003 3
• A dynamic index structure that adjusts gracefully to
inserts and deletes
• A balanced tree
• Leaf pages are not allocated sequentially. They are
linked together through pointers (a doubly linked list).
Index Entries
Data Entries
("Sequence set")
(Direct search)
Winter 2003 4
• Main characteristics:
– Insert/delete at logFN cost; keep tree height-balanced.
(F = fanout, N = # leaf pages)
– Minimum 50% occupancy (except for root). Each
node contains d <= m <= 2d entries. The parameter
d is called the order of the tree.
– Supports equality and range-searches efficiently.
3
Winter 2003 5
! "
• Same as that of ISAM
• Non-leaf nodes with m index entries contain m+1
pointers to children
• Pointer Pi points to a child with key values k such that ki k <>
• P0 points to a child whose key values are less than k1
Winter 2003 6
# $
• Search begins at root, and key comparisons direct it to a
leaf. At each node, a binary search or linear search can
be performed
• Search for 5*, 15*, all data entries >= 24* ...
• Based on the search for 15*, we know it is not in the tree!
Root
17 24 30
2* 3* 5* 7* 14* 16* 19* 20* 22* 24* 27* 29* 33* 34* 38* 39*
13
4
Winter 2003 7
% & #
• Find correct leaf L.
• Put data entry onto L.
– If L has enough space, done!
– Else, must split L (into L and a new node L2)
• Redistribute entries evenly, copy up middle key.
• Insert index entry pointing to L2 into parent of L.
• This can happen recursively
– To split index node, redistribute entries evenly, but
push up middle key. (Contrast with leaf splits.)
• Splits “grow” tree; root split increases height.
– Tree growth: gets wider or one level taller at top.
Winter 2003 8
% ' ( # $
• Observe how
minimum
occupancy is
guaranteed in both
leaf and index pg
splits.
• Note difference
between copy-up
and push-up; be
sure you
understand the
reasons for this.
2* 3* 5* 7* 8*
5
Entry to be inserted in parent node.
(Note that 5 is
continues to appear in the leaf.)
s copied up and
appears once in the index. Contrast
5 24 30
17
13
Entry to be inserted in parent node.
(Note that 17 is pushed up and only
this with a leaf split.)
5
Winter 2003 9
# $
"
% ' (
• Notice that root was split, leading to increase in height.
• In this example, we can avoid split by re-distributing
entries; however, this is usually not done in practice.
2* 3*
Root
17
24 30
14* 16* 19* 20* 22* 24* 27* 29* 33* 34* 38* 39*
13 5
7* 5* 8*
Winter 2003 10
# $
"
% ' (
2* 3*
Root
17
24 30
14* 16* 19* 20* 22* 24* 27* 29* 33* 34* 38* 39*
13 5
7* 5* 8*
• Notice that the value 5 occurs redundantly, once in a leaf
page and once in a non-leaf page. This is because
values in the leaf page cannot be pushed up, unlike the
value 17
6
Winter 2003 11
)
%
Root
17 24 30
2* 3* 5* 7* 14* 16* 19* 20* 22* 24* 27* 29* 33* 34* 38* 39*
13
Root
17 24 30
2* 3* 5* 7* 14* 16* 19* 20* 22* 24* 27* 29* 33* 34* 38* 39*
8
Insert 8*
8*
Winter 2003 12
)
%
• If a leaf node where insertion is to occur is full, fetch a
neighbour node (left or right).
• If neighbour node has space and same parent as full
node, redistribute entries and adjust parent nodes
accordingly
• Otherwise, if neighbour nodes are full or have a different
parent (i.e., not a sibling), then split as before.
7
Winter 2003 13
&
% & # "
• Start at root, find leaf L where entry belongs.
• Remove the entry.
– If L is at least half-full, done!
– If L has only d-1 entries,
• Try to re-distribute, borrowing from sibling (adjacent
node with same parent as L).
• If re-distribution fails, merge L and sibling.
• If merge occurred, must delete entry (pointing to L or
sibling) from parent of L.
• Merge could propagate to root, decreasing height.
Winter 2003 14
# $
"
*
% ' ( +
, &
% - . ( / 0 ( 1 1
• Deleting 19* is easy.
• Deleting 20* is done with re-distribution. Notice how
middle key is copied up.
2* 3*
Root
17
30
14* 16* 33* 34* 38* 39*
13 5
7* 5* 8* 22* 24*
27
27* 29*
8
Winter 2003 15
1 1
&
% / 2 (
• Must merge.
• Observe `toss’ of index
entry (on right), and `pull
down’ of index entry
(below).
30
22* 27* 29* 33* 34* 38* 39*
2* 3* 7* 14* 16* 22* 27* 29* 33* 34* 38* 39* 5* 8*
Root
30 13 5 17
Winter 2003 16
&
/ 2 (
2* 3*
Root
22
30
14* 16* 33* 34* 38* 39*
13 5
7* 5* 8* 22* 24*
27
27* 29*
17 20
17* 18* 20* 21*
9
Winter 2003 17
# $
" 3 4
" )
4
• Tree is shown below during deletion of 24*. (What could
be a possible initial tree?)
• In contrast to previous example, can re-distribute entry
from left child of root to right child.
Root
13 5 17 20
22
30
14* 16* 17* 18* 20* 33* 34* 38* 39* 22* 27* 29* 21* 7* 5* 8* 3* 2*
Winter 2003 18
"
)
4
• Intuitively, entries are re-distributed by `pushing through’
the splitting entry in the parent node.
• It suffices to re-distribute index entry with key 20; we’ve redistributed
17 as well for illustration.
14* 16* 33* 34* 38* 39* 22* 27* 29* 17* 18* 20* 21* 7* 5* 8* 2* 3*
Root
13 5
17
30 20 22
10
Winter 2003 19
5 % "
• If we have a large collection of records, and we want to
create a B+ tree on some field, doing so by repeatedly
inserting records is very slow.
• Bulk Loading for creating a B+ tree index on existing
records is much more efficient
Winter 2003 20
5 %
• Sort all data entries
– If data entries are (key, pointer) pairs, sort these pairs
according to key values and not the actual data
records
• Allocate an empty page to be the root. Insert pointer to
first (leaf) page in root page
3* 4* 6* 9* 10* 11* 12* 13* 20* 22* 23* 31* 35* 36* 38* 41* 44*
Sorted pages of data entries; not yet in B+ tree
Root
11
Winter 2003 21
5 %
• Add entry into root page for each page of sorted data
entries. Doubly linked data entry pages. Proceed until
root page is filled or no more data entry pages
3* 4* 6* 9* 10* 11* 12* 13* 20* 22* 23* 31* 35* 36* 38* 41* 44*
Root
6 10
Sorted pages of data entries; not yet in B+ tree
Winter 2003 22
5 %
• If root page is filled and to insert one more page of data
entries, split the root and create a new root page
3* 4* 6* 9* 10* 11* 12* 13* 20* 22* 23* 31* 35* 36* 38* 41* 44*
Root
6
Sorted pages of data entries; not yet in B+ tree
12
10
12
Winter 2003 23
5 %
• Index entries for leaf
pages always entered
into right-most index
page just above leaf
level. When this fills
up, it splits. (Split
may go up right-most
path to the root.)
• Much faster than
repeated inserts,
especially when one
considers locking!
3* 4* 6* 9* 10*11* 12*13* 20*22* 23* 31* 35*36* 38*41* 44*
Root
Data entry pages
not yet in B+ tree 35 23 12 6
10 20
3* 4* 6* 9* 10* 11* 12*13* 20*22* 23* 31* 35*36* 38*41* 44*
6
Root
10
12 23
20
35
38
not yet in B+ tree
Data entry pages
Winter 2003 24
"
5 %
• Option 1: multiple inserts.
– Slow.
– Does not give sequential storage of leaves.
• Option 2: Bulk Loading
– Has advantages for concurrency control.
– Fewer I/Os during build.
– Leaves will be stored sequentially (and linked, of
course).
– Can control “fill factor” on pages.
13
Winter 2003 25
• Many alternative file organizations exist, each
appropriate in some situation.
• If selection queries are frequent, sorting the file or
building an index is important.
– Hash-based indexes only good for equality
search.
– Sorted files and tree-based indexes best for range
search; also good for equality search. (Files rarely
kept sorted in practice; B+ tree index is better.)
• Index is a collection of data entries plus a way to
quickly find entries with given key values.
Winter 2003 26
• Data entries can be actual data records,
pairs, or pairs.
– Choice orthogonal to indexing technique used to
locate data entries with a given key value.
• Can have several indexes on a given file of data
records, each with a different search key.
• Indexes can be classified as clustered vs.
unclustered, primary vs. secondary, and dense vs.
sparse. Differences have important consequences
for utility/performance.
14
Winter 2003 27
• Tree-structured indexes are ideal for range-searches, also
good for equality searches.
• ISAM is a static structure.
– Only leaf pages modified; overflow pages needed.
– Overflow chains can degrade performance unless size
of data set and data distribution stay constant.
• B+ tree is a dynamic structure.
– Inserts/deletes leave tree height-balanced; logFN cost.
– High fanout (F) means depth rarely more than 3 or 4.
– Almost always better than maintaining a sorted file.
Winter 2003 28
• Recall that for any index, there are 3 alternatives for data
entries k*:
– Data record with key value k
–
–
– Choice orthogonal to the indexing technique
• Hash-based indexes are best for equality selections.
Cannot support range searches.
• Static and dynamic hashing techniques exist; trade-offs
similar to ISAM vs. B+ trees.
15
Winter 2003 29
6 %
• h(k) mod N returns the bucket to which data entry with
key k belongs. (N = # of buckets)
• We refer to the above as the hash function which maps
values into a range of buckets
h(key) mod N
h
key
Primary bucket pages Overflow pages
2
0
N-1
Winter 2003 30
6 %
• # primary pages fixed (which is N), allocated
sequentially, never de-allocated; overflow pages if
needed.
• Buckets contain data entries, which can be in any of the
three alternatives discussed earlier
• Hash function works on search key field of record r.
Must distribute values over range 0 ... N-1.
– Typically, h(key) = (a * key + b) for some constants a
and b
– lots known about how to tune h.
16
Winter 2003 31
6 %
• Search for data entry k: Apply hash function h on k to
obtain the bucket number. Then, search the bucket for k.
– Data entries in each bucket are typically maintained in
sorted order to speed up the search
• Insert a data entry k: Apply hash function h on k to
obtain the bucket number. Place data entry in that
bucket. If no space left, allocate a new overflow page and
place data entry in the overflow page. Chain up the
overflow page.
• Delete a data entry k: Search k and delete
Winter 2003 32
6 % 4 # $
• Assume 2 data entries per bucket and we have 5 buckets
• Insert key values a,b,c,d,e,f,g where h(a)=1, h(b)=2, h(c)=3, … h(g)=7
e
d
c
b,g
a,f
Insert z,x where
h(z)=1 and h(x)=5
1
2
3
4
5 e,x
d
c
b,g
a,f 1
2
3
4
5
z
e,x
d
c
b,g
a,f 1
2
3
4
5
z,p
Insert p,q,r where h(p)=1,
h(q)=5, and h(r)=1
r
q
17
Winter 2003 33
6 %
• Long overflow chains can develop and degrade
performance.
• Number of buckets is fixed. What if file shrinks
significantly through deletions?
– Extendible and Linear Hashing: Dynamic techniques
to fix this problem.
Winter 2003 34
#
6 %
• Situation: Bucket (primary page) becomes full. Why not reorganize
file by doubling number of buckets?
– Reading and writing all pages is expensive!
– Idea: Use directory of pointers to buckets, double # of
buckets by doubling the directory, splitting just the
bucket that overflowed!
– Directory much smaller than file, so doubling it is much
cheaper. Only one page of data entries is split. No
overflow page!
– Trick lies in how hash function is adjusted!
18
Winter 2003 35
# $
• Directory is array of
size 4.
• Search: To find bucket
for r, take last `global
depth’ # bits of h(r);
• If h(r) = 5 = binary 101,
it is in bucket pointed
to by 01.
13* 00
01
10
11
2
2
2
2
2
LOCAL DEPTH
GLOBAL DEPTH
DIRECTORY
Bucket A
Bucket B
Bucket C
Bucket D
DATA PAGES
10*
1* 21*
4* 12* 32* 16*
15* 7* 19*
5*
Winter 2003 36
Subscribe to:
Posts (Atom)